ftown-bridge 0.19.37 → 0.19.39

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.
Files changed (89) hide show
  1. package/dist/browser-access.js +2 -1
  2. package/dist/browser-access.js.map +1 -1
  3. package/dist/centrifugo-client.d.ts +1 -4
  4. package/dist/centrifugo-client.js +0 -29
  5. package/dist/centrifugo-client.js.map +1 -1
  6. package/dist/command-rpc.d.ts +1 -3
  7. package/dist/command-rpc.js +20 -74
  8. package/dist/command-rpc.js.map +1 -1
  9. package/dist/create-ftown-session.d.ts +0 -1
  10. package/dist/create-ftown-session.js +7 -13
  11. package/dist/create-ftown-session.js.map +1 -1
  12. package/dist/ftown-home.js +2 -2
  13. package/dist/ftown-sessions-cli.js +5 -340
  14. package/dist/ftown-sessions-cli.js.map +1 -1
  15. package/dist/harness-registry.d.ts +3 -22
  16. package/dist/harness-registry.js +3 -17
  17. package/dist/harness-registry.js.map +1 -1
  18. package/dist/index.js +10 -65
  19. package/dist/index.js.map +1 -1
  20. package/dist/install-ftown-skill.d.ts +5 -0
  21. package/dist/install-ftown-skill.js +91 -9
  22. package/dist/install-ftown-skill.js.map +1 -1
  23. package/dist/local-api-server.d.ts +1 -18
  24. package/dist/local-api-server.js +10 -108
  25. package/dist/local-api-server.js.map +1 -1
  26. package/dist/provider-env-store.js +1 -1
  27. package/dist/provider-env-store.js.map +1 -1
  28. package/dist/retired-automation.d.ts +14 -0
  29. package/dist/retired-automation.js +18 -0
  30. package/dist/retired-automation.js.map +1 -0
  31. package/dist/retired-skill-assets.d.ts +2 -0
  32. package/dist/retired-skill-assets.js +21 -0
  33. package/dist/retired-skill-assets.js.map +1 -0
  34. package/dist/session-controller.d.ts +2 -1
  35. package/dist/session-controller.js +5 -1
  36. package/dist/session-controller.js.map +1 -1
  37. package/dist/session-resurrection.d.ts +8 -2
  38. package/dist/session-resurrection.js +53 -17
  39. package/dist/session-resurrection.js.map +1 -1
  40. package/dist/solo/contract.d.ts +1 -1
  41. package/dist/solo/contract.js +2 -12
  42. package/dist/solo/contract.js.map +1 -1
  43. package/dist/solo/hub-manager.js +1 -1
  44. package/dist/terminal-pump.d.ts +1 -2
  45. package/dist/terminal-pump.js +1 -2
  46. package/dist/terminal-pump.js.map +1 -1
  47. package/dist/types.d.ts +4 -112
  48. package/dist/wire-types.d.ts +4 -48
  49. package/dist/wire-types.js +2 -0
  50. package/dist/wire-types.js.map +1 -1
  51. package/package.json +1 -2
  52. package/pi-extension/API.md +4 -12
  53. package/pi-extension/ftown.js +0 -138
  54. package/skills/ftown/SKILL.md +5 -27
  55. package/skills/ftown/agents/openai.yaml +2 -2
  56. package/skills/ftown/references/orchestrator.md +10 -65
  57. package/skills/ftown/references/sessions.md +3 -16
  58. package/skills/ftown/references/workflows.md +1 -3
  59. package/dist/loop-controller.d.ts +0 -87
  60. package/dist/loop-controller.js +0 -133
  61. package/dist/loop-controller.js.map +0 -1
  62. package/dist/loop-run-store.d.ts +0 -10
  63. package/dist/loop-run-store.js +0 -153
  64. package/dist/loop-run-store.js.map +0 -1
  65. package/dist/loop-schedule.d.ts +0 -18
  66. package/dist/loop-schedule.js +0 -35
  67. package/dist/loop-schedule.js.map +0 -1
  68. package/dist/loop-scheduler.d.ts +0 -148
  69. package/dist/loop-scheduler.js +0 -543
  70. package/dist/loop-scheduler.js.map +0 -1
  71. package/dist/loop-store.d.ts +0 -38
  72. package/dist/loop-store.js +0 -153
  73. package/dist/loop-store.js.map +0 -1
  74. package/dist/loop-validation.d.ts +0 -15
  75. package/dist/loop-validation.js +0 -91
  76. package/dist/loop-validation.js.map +0 -1
  77. package/skills/factory/SKILL.md +0 -198
  78. package/skills/factory/factory-template/bin/dispatch.py +0 -478
  79. package/skills/factory/factory-template/factory.yaml +0 -43
  80. package/skills/factory/factory-template/skills/_protocol.md +0 -105
  81. package/skills/factory/factory-template/skills/design.md +0 -260
  82. package/skills/factory/factory-template/skills/digest.md +0 -78
  83. package/skills/factory/factory-template/skills/groom.md +0 -243
  84. package/skills/factory/factory-template/skills/implement.md +0 -316
  85. package/skills/factory/factory-template/skills/pr.md +0 -221
  86. package/skills/factory/factory-template/skills/qa.md +0 -226
  87. package/skills/factory/factory-template/skills/review.md +0 -232
  88. package/skills/factory/factory-template/skills/triage.md +0 -157
  89. package/skills/ftown/references/loops.md +0 -93
@@ -43,6 +43,8 @@ export interface Session {
43
43
  shellType?: string;
44
44
  model?: string;
45
45
  parentSessionId?: string;
46
+ /** Historical automation provenance; new sessions cannot set it. */
47
+ loopId?: string;
46
48
  usage?: SessionUsage;
47
49
  }
48
50
  export type MailType = 'message' | 'task' | 'result' | 'escalation';
@@ -60,54 +62,8 @@ export interface MailMessage {
60
62
  deliveredAt?: string;
61
63
  deliveredVia?: string;
62
64
  }
63
- export type LoopHarness = 'claude' | 'cursor' | 'codex' | 'grok' | 'muse' | 'pi' | 'kimi-code' | 'opencode' | 'shell';
64
- export type LoopSchedule = {
65
- kind: 'interval';
66
- everyMs: number;
67
- } | {
68
- kind: 'cron';
69
- expression: string;
70
- tz?: string;
71
- };
72
- /** Body for POST /api/loops and PATCH /api/loops/:id (as a Partial). */
73
- export interface LoopDraft {
74
- name: string;
75
- schedule: LoopSchedule;
76
- harness: LoopHarness;
77
- workdir?: string;
78
- task: string;
79
- model?: string;
80
- enabled: boolean;
81
- overlapPolicy: 'skip' | 'allow';
82
- retention: {
83
- autoClearAfterRuns: number | null;
84
- };
85
- preflight?: {
86
- command: string;
87
- timeoutMs?: number;
88
- };
89
- postflight?: {
90
- command: string;
91
- timeoutMs?: number;
92
- runOnSkip?: boolean;
93
- };
94
- maxRuntimeMs?: number;
95
- group?: string;
96
- }
97
- /** Loop shape as returned by GET /api/loops and /api/loops/:id. */
98
- export interface LoopInfo extends LoopDraft {
99
- id: string;
100
- bridgeId: string;
101
- createdAt: string;
102
- updatedAt: string;
103
- lastRunAt?: string;
104
- nextRunAt?: string;
105
- lastStatus?: 'ok' | 'error' | 'running' | 'skipped';
106
- lastSessionId?: string;
107
- runCount: number;
108
- skipCount: number;
109
- runNowRequested?: boolean;
110
- }
65
+ export declare const AUTOMATION_REMOVED_CODE: "automation_removed";
66
+ export declare const AUTOMATION_REMOVED_MESSAGE = "Recurring loops and factory automation have been removed";
111
67
  /**
112
68
  * Keystrokes appended after `send`/`keys` text to submit it, keyed by shell type.
113
69
  *
@@ -7,6 +7,8 @@
7
7
  // are the shapes as seen over the wire by those CLIs — not necessarily identical to the
8
8
  // richer internal types in ./types.ts.
9
9
  export const MAIL_TYPES = ['message', 'task', 'result', 'escalation'];
10
+ export const AUTOMATION_REMOVED_CODE = 'automation_removed';
11
+ export const AUTOMATION_REMOVED_MESSAGE = 'Recurring loops and factory automation have been removed';
10
12
  /**
11
13
  * Keystrokes appended after `send`/`keys` text to submit it, keyed by shell type.
12
14
  *
@@ -1 +1 @@
1
- {"version":3,"file":"wire-types.js","sourceRoot":"","sources":["../src/wire-types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,mFAAmF;AACnF,6FAA6F;AAC7F,EAAE;AACF,mFAAmF;AACnF,sFAAsF;AACtF,wFAAwF;AACxF,uCAAuC;AAsDvC,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;AAsD3F;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,UAAmB;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB,CAAC,CAAc;IAC9C,OAAO,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"wire-types.js","sourceRoot":"","sources":["../src/wire-types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,mFAAmF;AACnF,6FAA6F;AAC7F,EAAE;AACF,mFAAmF;AACnF,sFAAsF;AACtF,wFAAwF;AACxF,uCAAuC;AAwDvC,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;AAgB3F,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAA6B,CAAC;AACrE,MAAM,CAAC,MAAM,0BAA0B,GAAG,0DAA0D,CAAC;AAErG;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,UAAmB;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB,CAAC,CAAc;IAC9C,OAAO,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ftown-bridge",
3
- "version": "0.19.37",
3
+ "version": "0.19.39",
4
4
  "description": "CLI bridge for ftown — generic PTY-over-Centrifugo relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,7 +54,6 @@
54
54
  "@xterm/headless": "^6.0.0",
55
55
  "centrifuge": "^5.2.2",
56
56
  "commander": "^13.1.0",
57
- "cron-parser": "^4.9.0",
58
57
  "express": "^5.3.0",
59
58
  "express-rate-limit": "^8.7.1",
60
59
  "jsonwebtoken": "^9.0.3",
@@ -8,7 +8,6 @@ The bundled Pi extension exposes ftown's authenticated local bridge API as a sma
8
8
  | --- | --- | --- | --- |
9
9
  | Session | Opaque UUID, or a unique exact name for lookup | Optional parent session; owns inbox, usage, screen, and log | Create, inspect, rename, reparent, stop, remove, revive |
10
10
  | Mail message | Assigned by the session inbox | Sent from one session to another; optional thread | Send, peek/read, delivered |
11
- | Loop | Opaque ID, or a unique exact name for lookup | Owns loop runs | Create, inspect, update, run, delete |
12
11
 
13
12
  All tools talk only to the local bridge selected by `FTOWN_HOOK_PORT` or `~/.ftown/bridge.json`. The extension supplies that bridge's bearer token and never sends credentials to the model.
14
13
 
@@ -31,15 +30,8 @@ All tools talk only to the local bridge selected by `FTOWN_HOOK_PORT` or `~/.fto
31
30
  | `ftown_session_manage` | `reparent` | `target`, `parent` | Updated session | Yes |
32
31
  | `ftown_session_manage` | `remove` | `target` | Removal acknowledgement | Yes |
33
32
  | `ftown_session_manage` | `revive` | `target` | Recreated session and resume state | Yes |
34
- | `ftown_loops` | `list` | None | `{ loops }` | No |
35
- | `ftown_loops` | `get` | `target` | `{ loop }` | No |
36
- | `ftown_loops` | `create` | `name`, `task`, `schedule` | `{ loop }` | Yes |
37
- | `ftown_loops` | `update` | `target` and changed fields | `{ loop }` | Yes |
38
- | `ftown_loops` | `delete` | `target` | Removal acknowledgement | Yes |
39
- | `ftown_loops` | `runs` | `target` | Loop run history | No |
40
- | `ftown_loops` | `run_now` | `target` | Requested loop run | Yes |
41
33
 
42
- `target` accepts an opaque ID or a unique exact name. Mail additionally accepts `parent`. Reparenting with `parent: null` clears the parent. Session and loop creation intentionally accept structured fields only; arbitrary session commands, environment variables, and loop preflight/postflight shell commands are not part of the model-facing contract.
34
+ `target` accepts an opaque ID or a unique exact name. Mail additionally accepts `parent`. Reparenting with `parent: null` clears the parent. Session creation intentionally accepts structured fields only; arbitrary session commands and environment variables are not part of the model-facing contract.
43
35
 
44
36
  The extension also registers `/ftown-mail read [--peek]`, `/ftown-mail send <session> <message>`, and `/ftown-sessions` for interactive use.
45
37
 
@@ -57,11 +49,11 @@ into the terminal.
57
49
 
58
50
  The local bridge bearer token authorizes access to the current ftown user's bridge resources. Session name resolution is performed against that same authenticated bridge. The model can inspect a terminal screen or search its captured log, but it cannot inject raw terminal keystrokes, resize terminals, clear terminal history, or call hook/conversation-resolution internals through these tools.
59
51
 
60
- Mutation executions are deduplicated in the running extension by `(tool name, Pi tool-call ID)`, including concurrent retries. Failed attempts are not cached. This prevents a retried model tool call from duplicating mail, sessions, management actions, or loop runs; it is not a durable idempotency key across Pi process restarts.
52
+ Mutation executions are deduplicated in the running extension by `(tool name, Pi tool-call ID)`, including concurrent retries. Failed attempts are not cached. This prevents a retried model tool call from duplicating mail, sessions, or management actions; it is not a durable idempotency key across Pi process restarts.
61
53
 
62
54
  ## Pagination and errors
63
55
 
64
- Inbox reads accept `limit` from 1 to 100. Screen and log operations accept zero-based `offset`; screen `limit` is 1 to 1,000, log `limit` is 1 to 1,000, and grep context is 0 to 10 lines. Session and loop collection endpoints currently return the bridge's complete local collection and inherit its unpaginated behavior.
56
+ Inbox reads accept `limit` from 1 to 100. Screen and log operations accept zero-based `offset`; screen `limit` is 1 to 1,000, log `limit` is 1 to 1,000, and grep context is 0 to 10 lines. Session collection endpoints currently return the bridge's complete local collection and inherit its unpaginated behavior.
65
57
 
66
58
  Transport failures, missing/ambiguous names, unavailable parent context, validation failures, and bridge `{ "error": string }` responses become Pi tool errors. No response body, token, stack trace, or upstream URL is exposed to the model.
67
59
 
@@ -71,4 +63,4 @@ The tool schemas ship with `ftown-bridge` and follow its semantic version. Addin
71
63
 
72
64
  ## Consumer walkthrough
73
65
 
74
- An agent can call `ftown_sessions.list`, use the returned session ID with `ftown_sessions.get` or `usage`, and then send context with `ftown_mail.send`. To delegate, it can create a child with `ftown_session_create`, retain the returned ID, inspect its output, and later rename, reparent, stop, remove, or revive it. For scheduled work, it can create or update a structured loop, request `run_now`, then inspect `runs` without leaving Pi.
66
+ An agent can call `ftown_sessions.list`, use the returned session ID with `ftown_sessions.get` or `usage`, and then send context with `ftown_mail.send`. To delegate, it can create a child with `ftown_session_create`, retain the returned ID, inspect its output, and later rename, reparent, stop, remove, or revive it.
@@ -638,144 +638,6 @@ export function registerFtownPiExtension(pi, options = {}) {
638
638
  },
639
639
  });
640
640
 
641
- async function listLoops() {
642
- const payload = await requestJson('/api/loops', { method: 'GET' });
643
- return Array.isArray(payload?.loops) ? payload.loops : [];
644
- }
645
-
646
- function resolveLoop(loops, target) {
647
- const normalized = target?.trim();
648
- if (!normalized) throw new Error('A loop id or name is required');
649
- const byId = loops.find((loop) => loop.id === normalized);
650
- if (byId) return byId;
651
- const byName = loops.filter((loop) => loop.name === normalized);
652
- if (byName.length === 1) return byName[0];
653
- if (byName.length > 1) throw new Error(`Multiple loops are named "${normalized}"; use an id`);
654
- throw new Error(`Loop not found: ${normalized}`);
655
- }
656
-
657
- const loopScheduleProperty = {
658
- type: 'object',
659
- properties: {
660
- kind: { type: 'string', enum: ['interval', 'cron'] },
661
- everyMs: { type: 'integer', minimum: 1000 },
662
- expression: { type: 'string', minLength: 1 },
663
- tz: { type: 'string', minLength: 1 },
664
- },
665
- required: ['kind'],
666
- additionalProperties: false,
667
- };
668
- const loopDraftProperties = {
669
- name: { type: 'string', minLength: 1 },
670
- task: { type: 'string', minLength: 1 },
671
- schedule: loopScheduleProperty,
672
- shell: { type: 'string', enum: ['claude', 'cursor', 'codex', 'grok', 'pi', 'kimi-code', 'opencode', 'shell'] },
673
- workdir: { type: 'string' },
674
- model: { type: 'string' },
675
- enabled: { type: 'boolean' },
676
- overlapPolicy: { type: 'string', enum: ['skip', 'allow'] },
677
- retention: { type: ['integer', 'null'], minimum: 0 },
678
- maxRuntimeMs: { type: 'integer', minimum: 1000 },
679
- group: { type: 'string' },
680
- };
681
- const loopDraftFields = Object.keys(loopDraftProperties);
682
-
683
- function validateLoopSchedule(schedule) {
684
- requireProperty({ schedule }, 'schedule');
685
- if (schedule === null || typeof schedule !== 'object') throw new Error('schedule is required');
686
- requireOperation({ operation: schedule.kind }, ['interval', 'cron']);
687
- if (schedule.kind === 'interval') requireProperty(schedule, 'everyMs', 'schedule.everyMs');
688
- if (schedule.kind === 'cron') requireString(schedule, 'expression', 'schedule.expression');
689
- }
690
-
691
- function validateLoopParams(params) {
692
- requireOperation(params, ['list', 'get', 'runs', 'run_now', 'delete', 'create', 'update']);
693
- if (['get', 'runs', 'run_now', 'delete', 'update'].includes(params.operation)) {
694
- requireString(params, 'target');
695
- }
696
- if (params.operation === 'create') {
697
- requireString(params, 'name');
698
- requireString(params, 'task');
699
- requireProperty(params, 'schedule');
700
- }
701
- if (params.operation === 'update' && !loopDraftFields.some((field) => hasOwn(params, field))) {
702
- throw new Error('At least one field to update is required');
703
- }
704
- if (hasOwn(params, 'schedule')) validateLoopSchedule(params.schedule);
705
- }
706
-
707
- function loopBody(params, create = false) {
708
- const body = {};
709
- for (const field of ['name', 'task', 'schedule', 'workdir', 'model', 'enabled', 'overlapPolicy', 'maxRuntimeMs', 'group']) {
710
- if (Object.prototype.hasOwnProperty.call(params, field)) body[field] = params[field];
711
- }
712
- if (Object.prototype.hasOwnProperty.call(params, 'shell')) body.harness = params.shell;
713
- if (Object.prototype.hasOwnProperty.call(params, 'retention')) {
714
- body.retention = { autoClearAfterRuns: params.retention };
715
- }
716
- if (create) {
717
- body.harness ??= 'pi';
718
- body.enabled ??= true;
719
- body.overlapPolicy ??= 'skip';
720
- body.retention ??= { autoClearAfterRuns: 10 };
721
- }
722
- return body;
723
- }
724
-
725
- pi.registerTool({
726
- name: 'ftown_loops',
727
- label: 'ftown loops',
728
- description: 'List, inspect, create, update, delete, or run scheduled ftown loops.',
729
- parameters: {
730
- type: 'object',
731
- properties: {
732
- operation: {
733
- type: 'string',
734
- enum: ['list', 'get', 'runs', 'run_now', 'delete', 'create', 'update'],
735
- },
736
- target: { type: 'string' },
737
- ...loopDraftProperties,
738
- },
739
- required: ['operation'],
740
- additionalProperties: false,
741
- },
742
- async execute(toolCallId, params) {
743
- try {
744
- validateLoopParams(params);
745
- if (params.operation === 'create') {
746
- return await executeOnce('ftown_loops', toolCallId, async () =>
747
- toolResult(await requestJson('/api/loops', {
748
- method: 'POST', body: JSON.stringify(loopBody(params, true)),
749
- })));
750
- }
751
- const loops = await listLoops();
752
- if (params.operation === 'list') return toolResult({ loops });
753
- const loop = resolveLoop(loops, params.target);
754
- const loopPath = `/api/loops/${encodeURIComponent(loop.id)}`;
755
- if (params.operation === 'get') {
756
- return toolResult(await requestJson(loopPath, { method: 'GET' }));
757
- }
758
- if (params.operation === 'runs') {
759
- return toolResult(await requestJson(`${loopPath}/runs`, { method: 'GET' }));
760
- }
761
- const mutate = async () => {
762
- if (params.operation === 'run_now') {
763
- return toolResult(await requestJson(`${loopPath}/run-now`, { method: 'POST' }));
764
- }
765
- if (params.operation === 'delete') {
766
- return toolResult(await requestJson(loopPath, { method: 'DELETE' }));
767
- }
768
- return toolResult(await requestJson(loopPath, {
769
- method: 'PATCH', body: JSON.stringify(loopBody(params)),
770
- }));
771
- };
772
- return await executeOnce('ftown_loops', toolCallId, mutate);
773
- } catch (error) {
774
- return toolError(error);
775
- }
776
- },
777
- });
778
-
779
641
  async function deliverMail(wait = 0, signal) {
780
642
  const messages = await drainMail(wait, signal);
781
643
  if (signal?.aborted || messages.length === 0) return 0;
@@ -2,31 +2,23 @@
2
2
  name: ftown
3
3
  description: >-
4
4
  Control ftown from inside agent sessions: create/list/read/drive sibling
5
- sessions, send ftown mail, manage recurring scheduled loops, coordinate worker
6
- agents through fticket when available, and run deterministic ftown-workflows. Use when a task mentions ftown,
7
- ftown sessions, bridge sessions, child/sibling agents, mail/inbox, scheduled
8
- loops, recurring agent runs, orchestration, fan-out, or ftown-workflows.
5
+ sessions, send ftown mail, and coordinate worker agent sessions. Use when a task
6
+ mentions ftown, ftown sessions, bridge sessions, child/sibling agents, mail/inbox,
7
+ orchestration, or fan-out.
9
8
  ---
10
9
 
11
10
  # ftown
12
11
 
13
12
  Use the local bridge CLIs installed under `~/.ftown`. They read
14
13
  `~/.ftown/bridge.json`; anyone who can read that file can control the bridge's
15
- sessions and loops.
14
+ sessions.
16
15
 
17
16
  ## Pick The Reference
18
17
 
19
18
  - Session control, mail, terminal output, archive/revive, or local API:
20
19
  read `references/sessions.md`.
21
- - Recurring scheduled work, cron/interval loops, loop run history:
22
- read `references/loops.md`.
23
20
  - Ad-hoc multi-agent coordination from an orchestrator session:
24
21
  read `references/orchestrator.md`.
25
- - Ticket-backed multi-agent coordination, durable status/context, dependencies, and
26
- resource leases: read the installed `fticket` skill. Prefer FTS over direct messages
27
- whenever an existing `FTS_DB`/ticket is available.
28
- - Scripted, resumable, deterministic multi-session fan-out:
29
- read `references/workflows.md`.
30
22
 
31
23
  ## Quick Commands
32
24
 
@@ -36,20 +28,6 @@ sessions and loops.
36
28
  ~/.ftown/ftown-sessions create --prompt "Run tests" --workdir "$PWD" --parent
37
29
  ~/.ftown/ftown-sessions screen <session-id> --limit 200
38
30
  ~/.ftown/ftown-sessions tell <session-id> --type task "continue with the API client"
39
-
40
- # Loops
41
- ~/.ftown/ftown-sessions loops
42
- ~/.ftown/ftown-sessions loop create --name repo-watch --every 30m --shell codex --workdir "$PWD" --task "Review recent changes"
43
- ~/.ftown/ftown-sessions loop run <loop-id>
44
-
45
- # Workflows
46
- ~/.ftown/ftown-workflows run path/to/script.mjs --workdir "$PWD"
47
-
48
- # FTS-backed coordination (when FTS_DB/TICKET_ID are supplied)
49
- fts show --db "$FTS_DB" "$TICKET_ID" --json
50
- fts resources --db "$FTS_DB" --json
51
31
  ```
52
32
 
53
- Prefer loops for unattended recurrence. Prefer workflows for deterministic
54
- control flow inside one run. Prefer orchestrator guidance for ad-hoc
55
- human-in-the-loop multi-agent coordination.
33
+ Use orchestrator guidance for ad-hoc multi-agent coordination.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "ftown"
3
- short_description: "Control ftown sessions, loops, orchestration, and workflows."
4
- default_prompt: "Use ftown to manage sessions, loops, or workflows."
3
+ short_description: "Control ftown sessions, mail, and orchestration."
4
+ default_prompt: "Use ftown to manage sessions and mail."
@@ -6,37 +6,6 @@ can coordinate worker agent sessions on this machine using the
6
6
 
7
7
  See `references/sessions.md` for the full session CLI reference.
8
8
 
9
- ## Coordination priority: FTS first
10
-
11
- FTS (`fticket`) is the primary coordination plane whenever the task supplies an
12
- `FTS_DB`/`TICKET_ID` or the repository already has `.ffactory/factory.db`. Read the
13
- installed `fticket` skill before operating it.
14
-
15
- - **Context:** read `fts show --db "$FTS_DB" "$TICKET_ID" --json` and the ticket's
16
- immutable artifact folder. Write durable handoffs there instead of sending them in chat.
17
- - **Status:** use claims, lifecycle transitions, renewals, notes, `fts board`, and
18
- `fts events`; these are the shared source of truth for every worker and orchestrator.
19
- - **Resources:** register named shared surfaces once, then require workers to acquire an
20
- exclusive/shared lease before use and release it afterward. A waitlisted lease means
21
- the worker must not touch that resource; the FTS scheduler promotes it FIFO.
22
- - **Dependencies:** express ordering with ticket dependencies rather than messages such as
23
- "wait until worker A is done."
24
-
25
- Do not initialize a new FTS database merely because one is absent unless creating that
26
- control plane is within the user's requested scope. When no FTS database/ticket exists,
27
- or when a fenced worker cannot write to FTS, ftown mail is the fallback. Use mail for
28
- urgent escalation that needs to wake a human/agent; do not duplicate routine context and
29
- status in both systems.
30
-
31
- Useful inspection commands:
32
-
33
- ```bash
34
- fts board --db "$FTS_DB"
35
- fts show --db "$FTS_DB" "$TICKET_ID" --json
36
- fts resources --db "$FTS_DB" --json
37
- fts events --db "$FTS_DB" --after <cursor>
38
- ```
39
-
40
9
  ## Spawning workers
41
10
 
42
11
  Omit `--shell` to inherit the orchestrator's current harness. Use `--shell` only
@@ -53,10 +22,9 @@ parent to `$FTOWN_SESSION_ID`.
53
22
  --prompt "Review the auth module and summarize findings"
54
23
  ```
55
24
 
56
- Returns JSON containing `session.id` — save it to inspect the worker later. For
57
- FTS-backed work, put `FTS_DB`, `TICKET_ID`, `WORKER_ID`, and the claim fence in the
58
- worker briefing; the ticket/artifacts remain the durable handoff. Workers spawned with
59
- `--parent` are also auto-briefed about the FTS-first policy and mail fallback.
25
+ Returns JSON containing `session.id` — save it to inspect the worker later.
26
+ Workers spawned with `--parent` are auto-briefed about reporting results and
27
+ escalations through mail, then removing themselves when finished.
60
28
  Provider-flavored workers require a bridge-host token configured with
61
29
  `ftown env set <flavor> <token>`; the bridge supplies their endpoint/model
62
30
  environment when it spawns them.
@@ -74,12 +42,10 @@ environment when it spawns them.
74
42
  ~/.ftown/ftown-sessions running <session-id>
75
43
  ```
76
44
 
77
- For FTS-backed work, observe `fts events --after <cursor>`, `fts show`, and
78
- `fts resources` rather than asking every worker for status. Use `screen` / `grep` to
79
- investigate a worker whose ticket/claim has gone quiet. When operating without FTS, end
80
- your turn and let fallback mail wake you instead of running a polling loop.
45
+ Use `screen` / `grep` to investigate a worker that has gone quiet. When waiting for
46
+ results, end your turn and let mail wake you instead of running a polling loop.
81
47
 
82
- ## Messaging fallback (mail)
48
+ ## Messaging (mail)
83
49
 
84
50
  Each session has an inbox. Claude and codex receive mail automatically at turn
85
51
  boundaries via hooks. Pi's bundled extension maintains a long-poll and wakes idle
@@ -101,15 +67,14 @@ ftown-harness mail send <child> --thread <id> "use the v2 endpoint"
101
67
  ftown-harness mail read
102
68
  ```
103
69
 
104
- Without FTS, children report back with `mail send --parent --type result` (or
105
- `--type escalation` when blocked). With FTS, use this only for an urgent escalation or
106
- when a fenced/unavailable database prevents recording the outcome. Fan-out still works via
70
+ Children report back with `mail send --parent --type result` (or
71
+ `--type escalation` when blocked). Fan-out works via
107
72
  `~/.ftown/ftown-sessions tell --children "pause and report status"` (now mail
108
73
  under the hood; one of `--parent | --children | --siblings`).
109
74
 
110
75
  If a worker is idle and must be woken right now, terminal keystroke injection
111
76
  (`ftown-harness send <id> "..." -s` or `tell --keys`) is the **last resort** —
112
- prefer an FTS update/event, then fallback mail, which arrives at the next turn boundary.
77
+ prefer mail, which arrives at the next turn boundary.
113
78
 
114
79
  ## Cleanup
115
80
 
@@ -126,30 +91,10 @@ Child briefings normally make workers report first and then remove themselves. E
126
91
  ask a worker to stay open when you need follow-up interaction; otherwise, clean up any
127
92
  worker that exits without honoring the self-removal instruction.
128
93
 
129
- ## Recurring work
130
-
131
- For unattended recurring work, create a scheduled loop instead of keeping an
132
- orchestrator alive to poll or sleep. See `references/loops.md` for the full loop
133
- workflow:
134
-
135
- ```bash
136
- ~/.ftown/ftown-sessions loop create \
137
- --name repo-watch \
138
- --every 30m \
139
- --shell codex \
140
- --workdir /path/to/repo \
141
- --task "Inspect recent changes, run the focused checks, and report issues"
142
- ```
143
-
144
- Loop runs show as normal ftown sessions grouped under their loop in the
145
- dashboard. Use `~/.ftown/ftown-sessions loop runs <loop-id>` to inspect run
146
- sessions from the CLI.
147
-
148
94
  ## Practical guidance
149
95
 
150
96
  - **Parallelize** independent tasks: spawn multiple workers before waiting for any.
151
- - **Observe shared state**: prefer `fts events`/`show`/`resources`; use `screen` / `grep`
152
- for diagnosis and mail only as fallback.
97
+ - **Follow worker results**: read incoming mail and use `screen` / `grep` for diagnosis.
153
98
  - **Name workers clearly**: `--name` shows in `list` output and as the sender
154
99
  name on incoming mail.
155
100
  - **Check `list` first**: see what's already running before spawning duplicates.
@@ -51,12 +51,6 @@ top-level `~/.ftown/ftown` dispatcher.
51
51
  ~/.ftown/ftown-sessions revive <session-id>
52
52
  ```
53
53
 
54
- ## Scheduled loops
55
-
56
- Loop runs are normal sessions tagged with `loopId`. For loop creation,
57
- schedule syntax, manual runs, pause/resume, and run history, read
58
- `references/loops.md`.
59
-
60
54
  ## Messaging (mail)
61
55
 
62
56
  `tell` posts to the target session's **inbox**. Mail is delivered into the
@@ -137,11 +131,9 @@ Spawned ftown sessions receive:
137
131
 
138
132
  Agent children (any `--shell` except `shell`) spawned with a parent also get an
139
133
  automatic one-paragraph briefing prepended to their first input: it states their
140
- name/id and parent name/id. When the task provides `FTS_DB` and `TICKET_ID`, the
141
- briefing makes fticket the primary coordination plane for context, status, and
142
- resource leases; mail is reserved for fallback/urgent escalation. Without FTS,
143
- it explains how to report via `ftown-harness mail send --parent --type result`.
144
- After recording or reporting the outcome, the child is instructed to remove itself as
134
+ name/id and parent name/id. It explains how to report via
135
+ `ftown-harness mail send --parent --type result` (or `--type escalation` when blocked).
136
+ After reporting the outcome, the child is instructed to remove itself as
145
137
  its final command unless the user explicitly asked it to stay open. The creator's
146
138
  `--prompt` follows after a `Task:` line.
147
139
 
@@ -171,11 +163,6 @@ The CLI wraps the loopback API. Raw access if needed:
171
163
  | DELETE | `/api/sessions/:id` | Remove (tombstone-archived) |
172
164
  | GET | `/api/archive` | List removed-session tombstones |
173
165
  | POST | `/api/sessions/:id/revive` | Recreate a removed session (new id) |
174
- | GET | `/api/loops` | List scheduled loops |
175
- | POST | `/api/loops` | Create a scheduled loop on this bridge |
176
- | GET/PATCH/DELETE | `/api/loops/:id` | Inspect, update, or delete a loop |
177
- | POST | `/api/loops/:id/run-now` | Request an immediate loop run; returns 404 `reason: not_found` if the loop no longer exists |
178
- | GET | `/api/loops/:id/runs` | List run sessions for a loop |
179
166
 
180
167
  To move a session under a different parent, PATCH it with the new parent's id:
181
168
 
@@ -11,9 +11,7 @@ This complements the by-hand orchestrator playbook in
11
11
  repeatable; use orchestrator guidance when you need to improvise or keep a
12
12
  human in the loop.
13
13
 
14
- This is separate from **scheduled loops** managed by `~/.ftown/ftown-sessions
15
- loop ...`. Scheduled loops create recurring ftown sessions over time. A workflow
16
- loop is control flow inside one deterministic workflow run.
14
+ A workflow runs deterministic control flow inside one explicitly started run.
17
15
 
18
16
  ## Operating Contract
19
17
 
@@ -1,87 +0,0 @@
1
- import type { Loop, LoopDraft, LoopRunRecord, Session } from './types.js';
2
- /**
3
- * Transport-agnostic loop operations, defined ONCE and shared by the two
4
- * dispatch surfaces (the Centrifugo RPC switch in index.ts and the local HTTP
5
- * router in local-api-server.ts). Methods take already-parsed typed input and
6
- * return typed results/errors; the adapters own all wire concerns (command
7
- * envelopes, HTTP statuses, response payload shaping).
8
- *
9
- * Error-propagation contract (matches the pre-refactor adapters byte for byte):
10
- * - `create`/`update` catch store/publish failures and return `code: 'failed'`
11
- * (the HTTP adapter mapped those to 400).
12
- * - `delete`/`runNow`/`runs` let unexpected failures THROW (the RPC adapter's
13
- * outer catch and the HTTP router's 500 handler mapped those before).
14
- */
15
- export type LoopErrorCode = 'invalid' | 'not_found' | 'failed';
16
- export interface LoopControllerError {
17
- ok: false;
18
- code: LoopErrorCode;
19
- message: string;
20
- }
21
- export type LoopControllerResult<T> = ({
22
- ok: true;
23
- } & T) | LoopControllerError;
24
- /** Outcome of a manual fire request. `fired: false` is NOT an error at the
25
- * controller level — the RPC adapter reports it as a successful response with
26
- * a reason, while the HTTP adapter maps `not_found` to a 404. */
27
- export type RunLoopNowOutcome = {
28
- fired: true;
29
- loop: Loop;
30
- } | {
31
- fired: false;
32
- reason: 'not_found' | 'overlap';
33
- };
34
- /** Injectable persistence surface (defaults to the real file-backed module). */
35
- export interface LoopStoreApi {
36
- createLoop(draft: LoopDraft): Loop;
37
- getLoop(id: string): Loop | undefined;
38
- listLoops(): Loop[];
39
- updateLoop(id: string, patch: Partial<LoopDraft>): Loop | null;
40
- deleteLoop(id: string): boolean;
41
- mutateLoopRuntime(id: string, fn: (loop: Loop) => void): Loop | null;
42
- }
43
- export interface LoopRunStoreApi {
44
- deleteLoopRunRecords(loopId: string): void;
45
- listLoopRunRecordsWithFallback(loopId: string, sessions: Session[], loadLog?: (sessionId: string) => Promise<string>): Promise<LoopRunRecord[]>;
46
- }
47
- export interface LoopControllerDeps {
48
- /** Loops are always owned by the bridge they were created on; every draft's
49
- * bridgeId is forced to this value. */
50
- bridgeId: string;
51
- scheduler: {
52
- kick(): void;
53
- onLoopDeleted(loop: Loop): void;
54
- };
55
- /** runner.isRunning — used by the run-now overlap guard. */
56
- isSessionRunning(sessionId: string): boolean;
57
- publishLoopUpdate(loop: Loop): Promise<void>;
58
- publishLoopRemoved(loopId: string): Promise<void>;
59
- /** Wire-safe (env-stripped) session list, for the loop-runs fallback. */
60
- listWireSessions(): Promise<Session[]>;
61
- loadTerminalLog(sessionId: string): Promise<string>;
62
- loopStore?: LoopStoreApi;
63
- loopRunStore?: LoopRunStoreApi;
64
- }
65
- export declare class LoopController {
66
- private readonly deps;
67
- private readonly loopStore;
68
- private readonly loopRunStore;
69
- constructor(deps: LoopControllerDeps);
70
- /** Create a loop owned by this bridge. bridgeId is forced to THIS bridge —
71
- * a loop is always owned by its runner, whatever the caller sent. */
72
- create(input: Partial<LoopDraft>): Promise<LoopControllerResult<{
73
- loop: Loop;
74
- }>>;
75
- list(): Loop[];
76
- get(loopId: string): LoopControllerResult<{
77
- loop: Loop;
78
- }>;
79
- update(loopId: string, patch: Partial<LoopDraft>): Promise<LoopControllerResult<{
80
- loop: Loop;
81
- }>>;
82
- delete(loopId: string): Promise<{
83
- removed: boolean;
84
- }>;
85
- runNow(loopId: string): Promise<RunLoopNowOutcome>;
86
- runs(loopId: string): Promise<LoopRunRecord[]>;
87
- }