@hotmeshio/long-tail 0.1.12 → 0.1.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Long Tail
2
2
 
3
- Turn your PostgreSQL database into a workflow engine with identity-aware durable execution, human-in-the-loop escalation, and MCP tool orchestration.
3
+ Long Tail turns your PostgreSQL database into a workflow engine where human and AI work share the same durable execution path. Hand off, pull back, escalate, automate without rewriting systems or losing continuity.
4
4
 
5
5
  ```bash
6
6
  npm install @hotmeshio/long-tail
@@ -4,6 +4,7 @@ exports.seedExamples = seedExamples;
4
4
  const hotmesh_1 = require("@hotmeshio/hotmesh");
5
5
  const defaults_1 = require("../modules/defaults");
6
6
  const logger_1 = require("../lib/logger");
7
+ const db_1 = require("../lib/db");
7
8
  const user_1 = require("../services/user");
8
9
  const roles_1 = require("../services/user/roles");
9
10
  const role_1 = require("../services/role");
@@ -211,11 +212,60 @@ async function seedEscalationChains() {
211
212
  }
212
213
  logger_1.loggerRegistry.info(`[examples] escalation chains verified (${SEED_CHAINS.length} entries)`);
213
214
  }
215
+ /**
216
+ * Seed example workflow configs into lt_config_workflows.
217
+ * Previously done by 002_seed.sql (which ran unconditionally).
218
+ * Now only runs when examples: true.
219
+ */
220
+ async function seedExampleConfigs() {
221
+ const pool = (0, db_1.getPool)();
222
+ await pool.query(`
223
+ INSERT INTO lt_config_workflows
224
+ (workflow_type, task_queue, default_role, invocable, description, tool_tags, envelope_schema, resolver_schema)
225
+ VALUES
226
+ ('reviewContent', 'long-tail-examples', 'reviewer', true,
227
+ 'Content review — AI-powered moderation with human escalation for low-confidence results',
228
+ ARRAY['document-processing', 'vision', 'ocr', 'translation'],
229
+ '{"data": {"contentId": "article-001", "content": "Content to review...", "contentType": "article"}, "metadata": {"source": "dashboard"}}'::jsonb,
230
+ '{"approved": true, "analysis": {"confidence": 0.95, "flags": [], "summary": "Manually reviewed and approved."}}'::jsonb),
231
+ ('verifyDocument', 'long-tail-examples', 'reviewer', true,
232
+ 'Document verification — AI Vision analyzes identity documents',
233
+ ARRAY['document-processing', 'vision', 'ocr', 'translation'],
234
+ '{"data": {"documentId": "doc-001", "documentUrl": "https://example.com/doc.jpg", "documentType": "drivers_license", "memberId": "member-12345"}, "metadata": {"source": "dashboard"}}'::jsonb,
235
+ '{"memberId": "", "extractedInfo": {}, "validationResult": "match", "confidence": 1.0}'::jsonb),
236
+ ('processClaim', 'long-tail-examples', 'reviewer', true,
237
+ 'Insurance claim processing — document analysis, validation, and human review',
238
+ ARRAY['document-processing', 'vision', 'database', 'query'],
239
+ '{"data": {"claimId": "CLM-2024-001", "claimantId": "POL-5551234", "claimType": "auto_collision", "amount": 12500, "documents": ["incident_report.pdf", "photo_evidence.jpg"]}, "metadata": {"source": "dashboard"}}'::jsonb,
240
+ '{"approved": true, "analysis": {"confidence": 0.92, "flags": [], "summary": "Documents reviewed and verified."}, "status": "resolved"}'::jsonb),
241
+ ('kitchenSink', 'long-tail-examples', 'reviewer', true,
242
+ 'Kitchen sink — demonstrates sleep, signals, parallel activities, escalation, and every durable primitive',
243
+ '{}',
244
+ '{"data": {"name": "World", "mode": "full"}, "metadata": {"source": "dashboard"}}'::jsonb,
245
+ NULL),
246
+ ('basicSignal', 'long-tail-examples', 'reviewer', true,
247
+ 'Signal-based escalation — workflow stays running while waiting for human input via conditionLT',
248
+ '{}',
249
+ '{"data": {"message": "Deployment approval needed for v2.1.0", "role": "reviewer"}, "metadata": {"certified": false, "source": "dashboard"}}'::jsonb,
250
+ '{"properties": {"approved": {"type": "boolean", "default": false, "description": "Approve this deployment?"}, "notes": {"type": "string", "default": "", "description": "Reviewer notes — visible to the workflow author"}}}'::jsonb)
251
+ ON CONFLICT (workflow_type) DO NOTHING
252
+ `);
253
+ // Assign roles to example workflows
254
+ await pool.query(`
255
+ INSERT INTO lt_config_roles (workflow_type, role)
256
+ SELECT workflow_type, unnest(ARRAY['reviewer', 'engineer', 'admin'])
257
+ FROM lt_config_workflows
258
+ WHERE workflow_type IN ('reviewContent', 'verifyDocument', 'processClaim', 'kitchenSink')
259
+ ON CONFLICT (workflow_type, role) DO NOTHING
260
+ `);
261
+ logger_1.loggerRegistry.info('[examples] workflow configs seeded');
262
+ }
214
263
  /**
215
264
  * Seed example workflows so the dashboard tells a story immediately.
216
265
  * Called automatically when `examples: true` is set in the start config.
217
266
  */
218
267
  async function seedExamples(client) {
268
+ await seedExampleConfigs();
219
269
  await seedRoles();
220
270
  await seedUsers();
221
271
  await seedEscalationChains();
@@ -1,5 +1,5 @@
1
1
  -- Long Tail Workflows: Schema
2
- -- Tasks track workflow executions; escalations track human interventions.
2
+ -- All tables, indexes, triggers, and functions.
3
3
 
4
4
  -- ─── updated_at trigger ─────────────────────────────────────────────────────
5
5
 
@@ -28,28 +28,31 @@ ON CONFLICT DO NOTHING;
28
28
  -- ─── lt_tasks ────────────────────────────────────────────────────────────────
29
29
 
30
30
  CREATE TABLE IF NOT EXISTS lt_tasks (
31
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
32
- workflow_id TEXT NOT NULL,
33
- workflow_type TEXT NOT NULL,
34
- lt_type TEXT NOT NULL,
35
- task_queue TEXT,
36
- status TEXT NOT NULL DEFAULT 'pending',
37
- priority INTEGER NOT NULL DEFAULT 2,
38
- signal_id TEXT NOT NULL,
31
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
32
+ workflow_id TEXT NOT NULL,
33
+ workflow_type TEXT NOT NULL,
34
+ lt_type TEXT NOT NULL,
35
+ task_queue TEXT,
36
+ status TEXT NOT NULL DEFAULT 'pending',
37
+ priority INTEGER NOT NULL DEFAULT 2,
38
+ signal_id TEXT NOT NULL,
39
39
  parent_workflow_id TEXT NOT NULL,
40
- origin_id TEXT,
41
- parent_id TEXT,
42
- started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
43
- completed_at TIMESTAMPTZ,
44
- envelope TEXT NOT NULL,
45
- metadata JSONB,
46
- error TEXT,
47
- milestones JSONB NOT NULL DEFAULT '[]'::JSONB,
48
- data TEXT,
49
- trace_id TEXT,
50
- span_id TEXT,
51
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
52
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
40
+ origin_id TEXT,
41
+ parent_id TEXT,
42
+ started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
43
+ completed_at TIMESTAMPTZ,
44
+ envelope TEXT NOT NULL,
45
+ metadata JSONB,
46
+ error TEXT,
47
+ milestones JSONB NOT NULL DEFAULT '[]'::JSONB,
48
+ data TEXT,
49
+ trace_id TEXT,
50
+ span_id TEXT,
51
+ initiated_by UUID,
52
+ principal_type TEXT DEFAULT 'user',
53
+ executing_as TEXT,
54
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
55
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
53
56
  );
54
57
 
55
58
  CREATE INDEX IF NOT EXISTS idx_lt_tasks_status_type ON lt_tasks (status, workflow_type, created_at DESC);
@@ -61,39 +64,80 @@ CREATE INDEX IF NOT EXISTS idx_lt_tasks_origin ON lt_tasks (origin_id, created_a
61
64
  CREATE INDEX IF NOT EXISTS idx_lt_tasks_workflow_id ON lt_tasks (workflow_id);
62
65
  CREATE INDEX IF NOT EXISTS idx_lt_tasks_origin_id ON lt_tasks (origin_id) WHERE origin_id IS NOT NULL;
63
66
  CREATE INDEX IF NOT EXISTS idx_lt_tasks_trace ON lt_tasks (trace_id) WHERE trace_id IS NOT NULL;
67
+ CREATE INDEX IF NOT EXISTS idx_lt_tasks_initiated_by ON lt_tasks (initiated_by) WHERE initiated_by IS NOT NULL;
64
68
 
65
69
  CREATE OR REPLACE TRIGGER trg_lt_tasks_updated_at
66
70
  BEFORE UPDATE ON lt_tasks
67
71
  FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
68
72
 
73
+ -- ─── lt_users ────────────────────────────────────────────────────────────────
74
+
75
+ CREATE TABLE IF NOT EXISTS lt_users (
76
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
77
+ external_id TEXT UNIQUE NOT NULL,
78
+ email TEXT,
79
+ display_name TEXT,
80
+ password_hash TEXT,
81
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended')),
82
+ account_type TEXT NOT NULL DEFAULT 'user' CHECK (account_type IN ('user', 'bot')),
83
+ oauth_provider TEXT,
84
+ oauth_provider_id TEXT,
85
+ metadata JSONB,
86
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
87
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
88
+ );
89
+
90
+ CREATE INDEX IF NOT EXISTS idx_lt_users_status ON lt_users (status);
91
+ CREATE INDEX IF NOT EXISTS idx_lt_users_oauth
92
+ ON lt_users (oauth_provider, oauth_provider_id)
93
+ WHERE oauth_provider IS NOT NULL;
94
+
95
+ CREATE TRIGGER lt_users_updated_at
96
+ BEFORE UPDATE ON lt_users
97
+ FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
98
+
99
+ -- ─── lt_user_roles ───────────────────────────────────────────────────────────
100
+
101
+ CREATE TABLE IF NOT EXISTS lt_user_roles (
102
+ user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
103
+ role TEXT NOT NULL REFERENCES lt_roles(role),
104
+ type TEXT NOT NULL DEFAULT 'member' CHECK (type IN ('superadmin', 'admin', 'member')),
105
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
106
+ PRIMARY KEY (user_id, role)
107
+ );
108
+
109
+ CREATE INDEX IF NOT EXISTS idx_lt_user_roles_type ON lt_user_roles (type);
110
+ CREATE INDEX IF NOT EXISTS idx_lt_user_roles_user_id ON lt_user_roles (user_id);
111
+
69
112
  -- ─── lt_escalations ─────────────────────────────────────────────────────────
70
113
 
71
114
  CREATE TABLE IF NOT EXISTS lt_escalations (
72
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
73
- type TEXT NOT NULL,
74
- subtype TEXT NOT NULL,
75
- description TEXT,
76
- status TEXT NOT NULL DEFAULT 'pending',
77
- priority INTEGER NOT NULL DEFAULT 2,
78
- task_id UUID REFERENCES lt_tasks(id),
79
- origin_id TEXT,
80
- parent_id TEXT,
81
- workflow_id TEXT,
82
- task_queue TEXT,
83
- workflow_type TEXT,
84
- role TEXT NOT NULL REFERENCES lt_roles(role),
85
- assigned_to TEXT,
86
- assigned_until TIMESTAMPTZ,
87
- resolved_at TIMESTAMPTZ,
88
- claimed_at TIMESTAMPTZ,
89
- envelope TEXT NOT NULL,
90
- metadata JSONB,
115
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
116
+ type TEXT NOT NULL,
117
+ subtype TEXT NOT NULL,
118
+ description TEXT,
119
+ status TEXT NOT NULL DEFAULT 'pending',
120
+ priority INTEGER NOT NULL DEFAULT 2,
121
+ task_id UUID REFERENCES lt_tasks(id),
122
+ origin_id TEXT,
123
+ parent_id TEXT,
124
+ workflow_id TEXT,
125
+ task_queue TEXT,
126
+ workflow_type TEXT,
127
+ role TEXT NOT NULL REFERENCES lt_roles(role),
128
+ assigned_to TEXT,
129
+ assigned_until TIMESTAMPTZ,
130
+ resolved_at TIMESTAMPTZ,
131
+ claimed_at TIMESTAMPTZ,
132
+ created_by UUID,
133
+ envelope TEXT NOT NULL,
134
+ metadata JSONB,
91
135
  escalation_payload TEXT,
92
- resolver_payload TEXT,
93
- trace_id TEXT,
94
- span_id TEXT,
95
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
96
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
136
+ resolver_payload TEXT,
137
+ trace_id TEXT,
138
+ span_id TEXT,
139
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
140
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
97
141
  );
98
142
 
99
143
  CREATE INDEX IF NOT EXISTS idx_lt_escalations_available ON lt_escalations (status, role, assigned_until, created_at DESC);
@@ -113,44 +157,12 @@ CREATE INDEX IF NOT EXISTS idx_lt_escalations_trace ON lt_escalations (trace_id)
113
157
  CREATE INDEX IF NOT EXISTS idx_lt_escalations_created_desc ON lt_escalations (created_at DESC);
114
158
  CREATE INDEX IF NOT EXISTS idx_lt_escalations_updated_desc ON lt_escalations (updated_at DESC);
115
159
  CREATE INDEX IF NOT EXISTS idx_lt_escalations_priority_desc ON lt_escalations (priority DESC, created_at DESC);
160
+ CREATE INDEX IF NOT EXISTS idx_lt_escalations_created_by ON lt_escalations (created_by) WHERE created_by IS NOT NULL;
116
161
 
117
162
  CREATE OR REPLACE TRIGGER trg_lt_escalations_updated_at
118
163
  BEFORE UPDATE ON lt_escalations
119
164
  FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
120
165
 
121
- -- ─── lt_users ────────────────────────────────────────────────────────────────
122
-
123
- CREATE TABLE IF NOT EXISTS lt_users (
124
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
125
- external_id TEXT UNIQUE NOT NULL,
126
- email TEXT,
127
- display_name TEXT,
128
- password_hash TEXT,
129
- status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended')),
130
- metadata JSONB,
131
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
132
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
133
- );
134
-
135
- CREATE TRIGGER lt_users_updated_at
136
- BEFORE UPDATE ON lt_users
137
- FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
138
-
139
- CREATE INDEX IF NOT EXISTS idx_lt_users_status ON lt_users (status);
140
-
141
- -- ─── lt_user_roles ───────────────────────────────────────────────────────────
142
-
143
- CREATE TABLE IF NOT EXISTS lt_user_roles (
144
- user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
145
- role TEXT NOT NULL REFERENCES lt_roles(role),
146
- type TEXT NOT NULL DEFAULT 'member' CHECK (type IN ('superadmin', 'admin', 'member')),
147
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
148
- PRIMARY KEY (user_id, role)
149
- );
150
-
151
- CREATE INDEX IF NOT EXISTS idx_lt_user_roles_type ON lt_user_roles (type);
152
- CREATE INDEX IF NOT EXISTS idx_lt_user_roles_user_id ON lt_user_roles (user_id);
153
-
154
166
  -- ─── lt_config_workflows ────────────────────────────────────────────────────
155
167
 
156
168
  CREATE TABLE IF NOT EXISTS lt_config_workflows (
@@ -193,24 +205,38 @@ CREATE TABLE IF NOT EXISTS lt_config_invocation_roles (
193
205
  UNIQUE(workflow_type, role)
194
206
  );
195
207
 
208
+ -- ─── lt_config_role_escalations ─────────────────────────────────────────────
209
+
210
+ CREATE TABLE IF NOT EXISTS lt_config_role_escalations (
211
+ source_role TEXT NOT NULL REFERENCES lt_roles(role),
212
+ target_role TEXT NOT NULL REFERENCES lt_roles(role),
213
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
214
+ PRIMARY KEY (source_role, target_role)
215
+ );
216
+
217
+ CREATE INDEX IF NOT EXISTS idx_lt_config_role_escalations_source
218
+ ON lt_config_role_escalations (source_role);
219
+
196
220
  -- ─── lt_mcp_servers ─────────────────────────────────────────────────────────
197
221
 
198
222
  CREATE TABLE IF NOT EXISTS lt_mcp_servers (
199
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
200
- name TEXT UNIQUE NOT NULL,
201
- description TEXT,
202
- transport_type TEXT NOT NULL CHECK (transport_type IN ('stdio', 'sse')),
203
- transport_config JSONB NOT NULL DEFAULT '{}'::JSONB,
204
- auto_connect BOOLEAN NOT NULL DEFAULT false,
205
- tool_manifest JSONB,
206
- status TEXT NOT NULL DEFAULT 'registered'
207
- CHECK (status IN ('registered', 'connected', 'error', 'disconnected')),
208
- last_connected_at TIMESTAMPTZ,
209
- metadata JSONB,
210
- tags TEXT[] NOT NULL DEFAULT '{}',
211
- compile_hints TEXT,
212
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
213
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
223
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
224
+ name TEXT UNIQUE NOT NULL,
225
+ description TEXT,
226
+ transport_type TEXT NOT NULL CHECK (transport_type IN ('stdio', 'sse', 'streamable-http')),
227
+ transport_config JSONB NOT NULL DEFAULT '{}'::JSONB,
228
+ auto_connect BOOLEAN NOT NULL DEFAULT false,
229
+ tool_manifest JSONB,
230
+ status TEXT NOT NULL DEFAULT 'registered'
231
+ CHECK (status IN ('registered', 'connected', 'error', 'disconnected')),
232
+ last_connected_at TIMESTAMPTZ,
233
+ metadata JSONB,
234
+ tags TEXT[] NOT NULL DEFAULT '{}',
235
+ compile_hints TEXT,
236
+ required_scopes TEXT[] NOT NULL DEFAULT '{}',
237
+ credential_providers TEXT[] NOT NULL DEFAULT '{}',
238
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
239
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
214
240
  );
215
241
 
216
242
  CREATE INDEX IF NOT EXISTS idx_lt_mcp_servers_name ON lt_mcp_servers (name);
@@ -222,18 +248,6 @@ CREATE OR REPLACE TRIGGER trg_lt_mcp_servers_updated_at
222
248
  BEFORE UPDATE ON lt_mcp_servers
223
249
  FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
224
250
 
225
- -- ─── lt_config_role_escalations ─────────────────────────────────────────────
226
-
227
- CREATE TABLE IF NOT EXISTS lt_config_role_escalations (
228
- source_role TEXT NOT NULL REFERENCES lt_roles(role),
229
- target_role TEXT NOT NULL REFERENCES lt_roles(role),
230
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
231
- PRIMARY KEY (source_role, target_role)
232
- );
233
-
234
- CREATE INDEX IF NOT EXISTS idx_lt_config_role_escalations_source
235
- ON lt_config_role_escalations (source_role);
236
-
237
251
  -- ─── lt_yaml_workflows ──────────────────────────────────────────────────────
238
252
 
239
253
  CREATE TABLE IF NOT EXISTS lt_yaml_workflows (
@@ -261,6 +275,12 @@ CREATE TABLE IF NOT EXISTS lt_yaml_workflows (
261
275
  cron_schedule TEXT,
262
276
  cron_envelope JSONB,
263
277
  execute_as TEXT,
278
+ original_prompt TEXT,
279
+ category TEXT,
280
+ search_vector TSVECTOR,
281
+ set_id UUID,
282
+ set_role TEXT CHECK (set_role IN ('leaf', 'composition', 'router')),
283
+ set_build_order INTEGER,
264
284
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
265
285
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
266
286
  );
@@ -268,11 +288,36 @@ CREATE TABLE IF NOT EXISTS lt_yaml_workflows (
268
288
  CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_status ON lt_yaml_workflows (status);
269
289
  CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_app_id ON lt_yaml_workflows (app_id);
270
290
  CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_tags ON lt_yaml_workflows USING GIN (tags);
291
+ CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_search ON lt_yaml_workflows USING GIN (search_vector);
292
+ CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_category ON lt_yaml_workflows (category) WHERE category IS NOT NULL;
293
+ CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_set_id ON lt_yaml_workflows (set_id) WHERE set_id IS NOT NULL;
294
+
295
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_yaml_workflows_app_topic_unique
296
+ ON lt_yaml_workflows (app_id, graph_topic)
297
+ WHERE status != 'archived';
271
298
 
272
299
  CREATE OR REPLACE TRIGGER trg_lt_yaml_workflows_updated_at
273
300
  BEFORE UPDATE ON lt_yaml_workflows
274
301
  FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
275
302
 
303
+ -- Full-text search trigger
304
+ CREATE OR REPLACE FUNCTION lt_yaml_workflows_search_vector_update()
305
+ RETURNS TRIGGER AS $$
306
+ BEGIN
307
+ NEW.search_vector :=
308
+ setweight(to_tsvector('english', coalesce(NEW.name, '')), 'A') ||
309
+ setweight(to_tsvector('english', coalesce(NEW.original_prompt, '')), 'A') ||
310
+ setweight(to_tsvector('english', coalesce(NEW.description, '')), 'B') ||
311
+ setweight(to_tsvector('english', coalesce(NEW.category, '')), 'C') ||
312
+ setweight(to_tsvector('english', coalesce(array_to_string(NEW.tags, ' '), '')), 'C');
313
+ RETURN NEW;
314
+ END;
315
+ $$ LANGUAGE plpgsql;
316
+
317
+ CREATE OR REPLACE TRIGGER trg_lt_yaml_workflows_search_vector
318
+ BEFORE INSERT OR UPDATE ON lt_yaml_workflows
319
+ FOR EACH ROW EXECUTE FUNCTION lt_yaml_workflows_search_vector_update();
320
+
276
321
  -- ─── lt_yaml_workflow_versions ──────────────────────────────────────────────
277
322
 
278
323
  CREATE TABLE IF NOT EXISTS lt_yaml_workflow_versions (
@@ -292,6 +337,136 @@ CREATE TABLE IF NOT EXISTS lt_yaml_workflow_versions (
292
337
  CREATE INDEX IF NOT EXISTS idx_lt_yaml_wf_versions_workflow
293
338
  ON lt_yaml_workflow_versions (workflow_id, version DESC);
294
339
 
340
+ -- ─── lt_workflow_sets ───────────────────────────────────────────────────────
341
+
342
+ CREATE TABLE IF NOT EXISTS lt_workflow_sets (
343
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
344
+ name TEXT UNIQUE NOT NULL,
345
+ description TEXT,
346
+ specification TEXT NOT NULL,
347
+ plan JSONB NOT NULL DEFAULT '[]'::JSONB,
348
+ namespaces TEXT[] NOT NULL DEFAULT '{}',
349
+ status TEXT NOT NULL DEFAULT 'planning'
350
+ CHECK (status IN ('planning','planned','building','deploying','completed','failed')),
351
+ source_workflow_id TEXT,
352
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
353
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
354
+ );
355
+
356
+ CREATE OR REPLACE TRIGGER trg_lt_workflow_sets_updated_at
357
+ BEFORE UPDATE ON lt_workflow_sets
358
+ FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
359
+
360
+ -- Add FK for lt_yaml_workflows.set_id (table already exists, add constraint)
361
+ DO $$ BEGIN
362
+ ALTER TABLE lt_yaml_workflows ADD CONSTRAINT fk_lt_yaml_workflows_set_id
363
+ FOREIGN KEY (set_id) REFERENCES lt_workflow_sets(id) ON DELETE SET NULL;
364
+ EXCEPTION
365
+ WHEN duplicate_object THEN NULL;
366
+ END $$;
367
+
368
+ -- ─── lt_oauth_tokens ────────────────────────────────────────────────────────
369
+
370
+ CREATE TABLE IF NOT EXISTS lt_oauth_tokens (
371
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
372
+ user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
373
+ provider TEXT NOT NULL,
374
+ label TEXT NOT NULL DEFAULT 'default',
375
+ access_token_enc TEXT NOT NULL,
376
+ refresh_token_enc TEXT,
377
+ token_type TEXT NOT NULL DEFAULT 'bearer',
378
+ scopes TEXT[] NOT NULL DEFAULT '{}',
379
+ expires_at TIMESTAMPTZ,
380
+ provider_user_id TEXT NOT NULL,
381
+ provider_email TEXT,
382
+ metadata JSONB,
383
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
384
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
385
+ UNIQUE (user_id, provider, label)
386
+ );
387
+
388
+ CREATE INDEX IF NOT EXISTS idx_lt_oauth_tokens_provider
389
+ ON lt_oauth_tokens (provider, user_id);
390
+
391
+ CREATE OR REPLACE TRIGGER trg_lt_oauth_tokens_updated_at
392
+ BEFORE UPDATE ON lt_oauth_tokens
393
+ FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
394
+
395
+ -- ─── lt_service_tokens ──────────────────────────────────────────────────────
396
+
397
+ CREATE TABLE IF NOT EXISTS lt_service_tokens (
398
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
399
+ name TEXT UNIQUE NOT NULL,
400
+ token_hash TEXT NOT NULL,
401
+ server_id UUID REFERENCES lt_mcp_servers(id) ON DELETE CASCADE,
402
+ scopes TEXT[] NOT NULL DEFAULT '{}',
403
+ expires_at TIMESTAMPTZ,
404
+ last_used_at TIMESTAMPTZ,
405
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
406
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
407
+ );
408
+
409
+ CREATE INDEX IF NOT EXISTS idx_lt_service_tokens_server
410
+ ON lt_service_tokens (server_id);
411
+
412
+ CREATE OR REPLACE TRIGGER trg_lt_service_tokens_updated_at
413
+ BEFORE UPDATE ON lt_service_tokens
414
+ FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
415
+
416
+ -- ─── lt_bot_api_keys ────────────────────────────────────────────────────────
417
+
418
+ CREATE TABLE IF NOT EXISTS lt_bot_api_keys (
419
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
420
+ name TEXT NOT NULL,
421
+ user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
422
+ key_hash TEXT NOT NULL,
423
+ scopes TEXT[] NOT NULL DEFAULT '{}',
424
+ expires_at TIMESTAMPTZ,
425
+ last_used_at TIMESTAMPTZ,
426
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
427
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
428
+ UNIQUE (user_id, name)
429
+ );
430
+
431
+ CREATE INDEX IF NOT EXISTS idx_bot_api_keys_user_id ON lt_bot_api_keys (user_id);
432
+
433
+ -- ─── lt_ephemeral_credentials ───────────────────────────────────────────────
434
+
435
+ CREATE TABLE IF NOT EXISTS lt_ephemeral_credentials (
436
+ token UUID PRIMARY KEY DEFAULT gen_random_uuid(),
437
+ value BYTEA NOT NULL,
438
+ label TEXT,
439
+ max_uses INTEGER NOT NULL DEFAULT 0,
440
+ use_count INTEGER NOT NULL DEFAULT 0,
441
+ expires_at TIMESTAMPTZ,
442
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
443
+ );
444
+
445
+ CREATE INDEX IF NOT EXISTS idx_lt_ephemeral_expiry
446
+ ON lt_ephemeral_credentials (expires_at)
447
+ WHERE expires_at IS NOT NULL;
448
+
449
+ -- ─── lt_knowledge ───────────────────────────────────────────────────────────
450
+
451
+ CREATE TABLE IF NOT EXISTS lt_knowledge (
452
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
453
+ domain TEXT NOT NULL,
454
+ key TEXT NOT NULL,
455
+ data JSONB NOT NULL DEFAULT '{}',
456
+ tags TEXT[] NOT NULL DEFAULT '{}',
457
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
458
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
459
+ UNIQUE(domain, key)
460
+ );
461
+
462
+ CREATE INDEX IF NOT EXISTS idx_lt_knowledge_domain ON lt_knowledge (domain);
463
+ CREATE INDEX IF NOT EXISTS idx_lt_knowledge_tags ON lt_knowledge USING GIN (tags);
464
+ CREATE INDEX IF NOT EXISTS idx_lt_knowledge_data ON lt_knowledge USING GIN (data);
465
+
466
+ CREATE TRIGGER lt_knowledge_updated_at
467
+ BEFORE UPDATE ON lt_knowledge
468
+ FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
469
+
295
470
  -- ─── lt_namespaces ──────────────────────────────────────────────────────────
296
471
 
297
472
  CREATE TABLE IF NOT EXISTS lt_namespaces (
@@ -1,6 +1,7 @@
1
- -- Seed data: workflow configs, MCP servers, escalation chains.
1
+ -- System seed data: built-in MCP server, system workflow configs, escalation chains.
2
+ -- Example workflow configs are seeded at runtime when examples: true.
2
3
 
3
- -- ─── MCP servers ────────────────────────────────────────────────────────────
4
+ -- ─── Built-in MCP server ───────────────────────────────────────────────────
4
5
 
5
6
  INSERT INTO lt_mcp_servers (name, description, transport_type, transport_config, auto_connect, status)
6
7
  VALUES (
@@ -24,52 +25,68 @@ INSERT INTO lt_config_role_escalations (source_role, target_role) VALUES
24
25
  ('admin', 'superadmin')
25
26
  ON CONFLICT DO NOTHING;
26
27
 
27
- -- ─── Example workflows (all directly invocable) ────────────────────────────
28
+ -- ─── System workflow configs ────────────────────────────────────────────────
28
29
 
29
30
  INSERT INTO lt_config_workflows
30
- (workflow_type, task_queue, default_role, invocable, description, tool_tags, envelope_schema, resolver_schema)
31
+ (workflow_type, task_queue, default_role, invocable, description, tool_tags)
31
32
  VALUES
32
- -- Review content
33
- ('reviewContent', 'long-tail-examples', 'reviewer', true,
34
- 'Content review — AI-powered moderation with human escalation for low-confidence results',
35
- ARRAY['document-processing', 'vision', 'ocr', 'translation'],
36
- '{"data": {"contentId": "article-001", "content": "Content to review...", "contentType": "article"}, "metadata": {"source": "dashboard"}}'::jsonb,
37
- '{"approved": true, "analysis": {"confidence": 0.95, "flags": [], "summary": "Manually reviewed and approved."}}'::jsonb),
33
+ ('mcpQuery', 'long-tail-system', 'engineer', false,
34
+ 'Dynamic MCP tool orchestration — LLM agentic loop with raw MCP tools',
35
+ '{}'),
36
+ ('mcpTriage', 'long-tail-system', 'engineer', false,
37
+ 'Dynamic MCP triage LLM agentic loop for escalation remediation',
38
+ '{}'),
39
+ ('mcpWorkflowBuilder', 'long-tail-system', 'engineer', false,
40
+ 'Direct pipeline builder — LLM constructs DAG from tool schemas',
41
+ '{}'),
42
+ ('mcpWorkflowPlanner', 'long-tail-system', 'engineer', false,
43
+ 'Plan mode — decomposes specifications into multi-workflow sets',
44
+ '{}')
45
+ ON CONFLICT (workflow_type) DO NOTHING;
38
46
 
39
- -- Verify document
40
- ('verifyDocument', 'long-tail-examples', 'reviewer', true,
41
- 'Document verification AI Vision analyzes identity documents',
42
- ARRAY['document-processing', 'vision', 'ocr', 'translation'],
43
- '{"data": {"documentId": "doc-001", "documentUrl": "https://example.com/doc.jpg", "documentType": "drivers_license", "memberId": "member-12345"}, "metadata": {"source": "dashboard"}}'::jsonb,
44
- '{"memberId": "", "extractedInfo": {}, "validationResult": "match", "confidence": 1.0}'::jsonb),
47
+ -- Query router (orchestrator entry point)
48
+ INSERT INTO lt_config_workflows
49
+ (workflow_type, task_queue, default_role, invocable, description, tool_tags, envelope_schema)
50
+ VALUES
51
+ ('mcpQueryRouter', 'long-tail-system', 'engineer', true,
52
+ 'Do anything with tools — browser automation, file operations, HTTP requests, database queries, document processing, and more',
53
+ '{}',
54
+ '{"data": {"prompt": "Describe what you want to accomplish using available tools..."}, "metadata": {"source": "dashboard"}}'::jsonb)
55
+ ON CONFLICT (workflow_type) DO NOTHING;
45
56
 
46
- -- Process claim
47
- ('processClaim', 'long-tail-examples', 'reviewer', true,
48
- 'Insurance claim processing — document analysis, validation, and human review',
49
- ARRAY['document-processing', 'vision', 'database', 'query'],
50
- '{"data": {"claimId": "CLM-2024-001", "claimantId": "POL-5551234", "claimType": "auto_collision", "amount": 12500, "documents": ["incident_report.pdf", "photo_evidence.jpg"]}, "metadata": {"source": "dashboard"}}'::jsonb,
51
- '{"approved": true, "analysis": {"confidence": 0.92, "flags": [], "summary": "Documents reviewed and verified."}, "status": "resolved"}'::jsonb),
57
+ -- Deterministic execution (compiled YAML workflows)
58
+ INSERT INTO lt_config_workflows
59
+ (workflow_type, task_queue, default_role, invocable, description, tool_tags)
60
+ VALUES
61
+ ('mcpDeterministic', 'long-tail-system', 'engineer', false,
62
+ 'Deterministic execution invokes matched compiled YAML workflows with extracted inputs',
63
+ '{}')
64
+ ON CONFLICT (workflow_type) DO NOTHING;
52
65
 
53
- -- Kitchen sink
54
- ('kitchenSink', 'long-tail-examples', 'reviewer', true,
55
- 'Kitchen sink — demonstrates sleep, signals, parallel activities, escalation, and every durable primitive',
56
- '{}',
57
- '{"data": {"name": "World", "mode": "full"}, "metadata": {"source": "dashboard"}}'::jsonb,
58
- NULL),
66
+ -- Triage router
67
+ INSERT INTO lt_config_workflows
68
+ (workflow_type, task_queue, default_role, invocable, description, tool_tags)
69
+ VALUES
70
+ ('mcpTriageRouter', 'long-tail-system', 'engineer', false,
71
+ 'Triage router — discovers compiled workflows for remediation, routes to deterministic or dynamic triage',
72
+ '{}')
73
+ ON CONFLICT (workflow_type) DO NOTHING;
59
74
 
60
- -- Basic signal (configured, NOT certified — no escalation roles)
61
- ('basicSignal', 'long-tail-examples', 'reviewer', true,
62
- 'Signal-based escalation workflow stays running while waiting for human input via conditionLT',
63
- '{}',
64
- '{"data": {"message": "Deployment approval needed for v2.1.0", "role": "reviewer"}, "metadata": {"certified": false, "source": "dashboard"}}'::jsonb,
65
- '{"properties": {"approved": {"type": "boolean", "default": false, "description": "Approve this deployment?"}, "notes": {"type": "string", "default": "", "description": "Reviewer notes — visible to the workflow author"}}}'::jsonb)
75
+ -- Triage deterministic
76
+ INSERT INTO lt_config_workflows
77
+ (workflow_type, task_queue, default_role, invocable, description, tool_tags)
78
+ VALUES
79
+ ('mcpTriageDeterministic', 'long-tail-system', 'engineer', false,
80
+ 'Deterministic triage invokes matched compiled workflows for escalation remediation',
81
+ '{}')
66
82
  ON CONFLICT (workflow_type) DO NOTHING;
67
83
 
68
- -- ─── Assign roles to all workflows ──────────────────────────────────────────
84
+ -- ─── Assign roles to all system workflows ──────────────────────────────────
69
85
 
70
86
  INSERT INTO lt_config_roles (workflow_type, role)
71
- SELECT workflow_type, unnest(ARRAY['reviewer', 'engineer', 'admin'])
72
- FROM lt_config_workflows
73
- WHERE workflow_type != 'basicSignal'
87
+ SELECT wt, unnest(ARRAY['reviewer', 'engineer', 'admin'])
88
+ FROM unnest(ARRAY[
89
+ 'mcpQuery', 'mcpTriage', 'mcpWorkflowBuilder', 'mcpWorkflowPlanner',
90
+ 'mcpQueryRouter', 'mcpDeterministic', 'mcpTriageRouter', 'mcpTriageDeterministic'
91
+ ]) AS wt
74
92
  ON CONFLICT (workflow_type, role) DO NOTHING;
75
-
@@ -64,6 +64,10 @@ const claimAndResolveSchema = zod_1.z.object({
64
64
  resolver_id: zod_1.z.string().describe('Identifier for who/what is resolving'),
65
65
  payload: zod_1.z.record(zod_1.z.any()).describe('Resolution payload data'),
66
66
  });
67
+ const resolveEscalationSchema = zod_1.z.object({
68
+ escalation_id: zod_1.z.string().describe('The escalation ID to resolve'),
69
+ payload: zod_1.z.record(zod_1.z.any()).describe('Resolution payload data'),
70
+ });
67
71
  const escalateAndWaitSchema = zod_1.z.object({
68
72
  role: zod_1.z.string().describe('Target role for the escalation (e.g., "reviewer")'),
69
73
  message: zod_1.z.string().describe('Description of what input is needed from the human'),
@@ -218,6 +222,33 @@ async function createHumanQueueServer(options) {
218
222
  }],
219
223
  };
220
224
  });
225
+ // ── resolve_escalation ──────────────────────────────────────────────
226
+ server.registerTool('resolve_escalation', {
227
+ title: 'Resolve Escalation',
228
+ description: 'Resolve an already-claimed escalation with a payload. Use when the claim happened externally (e.g. via API).',
229
+ inputSchema: resolveEscalationSchema,
230
+ }, async (args) => {
231
+ const resolved = await escalationService.resolveEscalation(args.escalation_id, args.payload);
232
+ if (!resolved) {
233
+ return {
234
+ content: [{
235
+ type: 'text',
236
+ text: JSON.stringify({ error: 'Failed to resolve escalation' }),
237
+ }],
238
+ isError: true,
239
+ };
240
+ }
241
+ return {
242
+ content: [{
243
+ type: 'text',
244
+ text: JSON.stringify({
245
+ escalation_id: resolved.id,
246
+ status: resolved.status,
247
+ resolved_at: resolved.resolved_at,
248
+ }),
249
+ }],
250
+ };
251
+ });
221
252
  // ── escalate_and_wait ──────────────────────────────────────────────
222
253
  server.registerTool('escalate_and_wait', {
223
254
  title: 'Escalate and Wait',
@@ -64,6 +64,10 @@ const claimAndResolveSchema = zod_1.z.object({
64
64
  resolver_id: zod_1.z.string().describe('Identifier for who/what is resolving'),
65
65
  payload: zod_1.z.record(zod_1.z.any()).describe('Resolution payload data'),
66
66
  });
67
+ const resolveEscalationSchema = zod_1.z.object({
68
+ escalation_id: zod_1.z.string().describe('The escalation ID to resolve'),
69
+ payload: zod_1.z.record(zod_1.z.any()).describe('Resolution payload data'),
70
+ });
67
71
  const escalateAndWaitSchema = zod_1.z.object({
68
72
  role: zod_1.z.string().describe('Target role for the escalation (e.g., "reviewer")'),
69
73
  message: zod_1.z.string().describe('Description of what input is needed from the human'),
@@ -218,6 +222,33 @@ async function createHumanQueueServer(options) {
218
222
  }],
219
223
  };
220
224
  });
225
+ // ── resolve_escalation ──────────────────────────────────────────────
226
+ server.registerTool('resolve_escalation', {
227
+ title: 'Resolve Escalation',
228
+ description: 'Resolve an already-claimed escalation with a payload. Use when the claim happened externally (e.g. via API).',
229
+ inputSchema: resolveEscalationSchema,
230
+ }, async (args) => {
231
+ const resolved = await escalationService.resolveEscalation(args.escalation_id, args.payload);
232
+ if (!resolved) {
233
+ return {
234
+ content: [{
235
+ type: 'text',
236
+ text: JSON.stringify({ error: 'Failed to resolve escalation' }),
237
+ }],
238
+ isError: true,
239
+ };
240
+ }
241
+ return {
242
+ content: [{
243
+ type: 'text',
244
+ text: JSON.stringify({
245
+ escalation_id: resolved.id,
246
+ status: resolved.status,
247
+ resolved_at: resolved.resolved_at,
248
+ }),
249
+ }],
250
+ };
251
+ });
221
252
  // ── escalate_and_wait ──────────────────────────────────────────────
222
253
  server.registerTool('escalate_and_wait', {
223
254
  title: 'Escalate and Wait',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -61,7 +61,7 @@
61
61
  "dependencies": {
62
62
  "@anthropic-ai/sdk": "^0.92.0",
63
63
  "@aws-sdk/client-s3": "^3.1017.0",
64
- "@hotmeshio/hotmesh": "^0.14.4",
64
+ "@hotmeshio/hotmesh": "^0.14.7",
65
65
  "@modelcontextprotocol/sdk": "^1.27.1",
66
66
  "@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
67
67
  "@opentelemetry/resources": "^2.5.1",
@@ -1,39 +0,0 @@
1
- -- Workflow discovery: full-text search, original prompt, and category.
2
-
3
- -- Original prompt that spawned this workflow (richest semantic signal)
4
- ALTER TABLE lt_yaml_workflows ADD COLUMN IF NOT EXISTS original_prompt TEXT;
5
-
6
- -- Capability category derived from tool usage patterns
7
- ALTER TABLE lt_yaml_workflows ADD COLUMN IF NOT EXISTS category TEXT;
8
-
9
- -- Full-text search vector, auto-maintained by trigger
10
- ALTER TABLE lt_yaml_workflows ADD COLUMN IF NOT EXISTS search_vector TSVECTOR;
11
-
12
- -- GIN index on search_vector for fast full-text search
13
- CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_search
14
- ON lt_yaml_workflows USING GIN (search_vector);
15
-
16
- -- Index on category for filtered queries
17
- CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_category
18
- ON lt_yaml_workflows (category) WHERE category IS NOT NULL;
19
-
20
- -- Trigger: rebuild search_vector from name, description, tags, original_prompt, category
21
- CREATE OR REPLACE FUNCTION lt_yaml_workflows_search_vector_update()
22
- RETURNS TRIGGER AS $$
23
- BEGIN
24
- NEW.search_vector :=
25
- setweight(to_tsvector('english', coalesce(NEW.name, '')), 'A') ||
26
- setweight(to_tsvector('english', coalesce(NEW.original_prompt, '')), 'A') ||
27
- setweight(to_tsvector('english', coalesce(NEW.description, '')), 'B') ||
28
- setweight(to_tsvector('english', coalesce(NEW.category, '')), 'C') ||
29
- setweight(to_tsvector('english', coalesce(array_to_string(NEW.tags, ' '), '')), 'C');
30
- RETURN NEW;
31
- END;
32
- $$ LANGUAGE plpgsql;
33
-
34
- CREATE OR REPLACE TRIGGER trg_lt_yaml_workflows_search_vector
35
- BEFORE INSERT OR UPDATE ON lt_yaml_workflows
36
- FOR EACH ROW EXECUTE FUNCTION lt_yaml_workflows_search_vector_update();
37
-
38
- -- Backfill search_vector for existing rows
39
- UPDATE lt_yaml_workflows SET updated_at = NOW();
@@ -1,38 +0,0 @@
1
- -- Split mcpQuery into router + dynamic + deterministic workflows.
2
- -- mcpQueryRouter is the new entry point (orchestrator).
3
- -- mcpQuery becomes dynamic-only (leaf).
4
- -- mcpDeterministic invokes compiled YAML workflows (leaf).
5
-
6
- -- Update existing mcpQuery: no longer directly invocable (called via router)
7
- UPDATE lt_config_workflows
8
- SET invocable = false,
9
- description = 'Dynamic MCP tool orchestration — LLM agentic loop with raw MCP tools'
10
- WHERE workflow_type = 'mcpQuery';
11
-
12
- -- Add mcpQueryRouter (orchestrator — the new entry point)
13
- INSERT INTO lt_config_workflows
14
- (workflow_type, task_queue, default_role, invocable, description, tool_tags, envelope_schema)
15
- VALUES
16
- ('mcpQueryRouter', 'long-tail-system', 'engineer', true,
17
- 'Do anything with tools — browser automation, file operations, HTTP requests, database queries, document processing, and more',
18
- '{}',
19
- '{"data": {"prompt": "Describe what you want to accomplish using available tools..."}, "metadata": {"source": "dashboard"}}'::jsonb)
20
- ON CONFLICT (workflow_type) DO NOTHING;
21
-
22
- -- Add mcpDeterministic (leaf — invokes compiled YAML workflows)
23
- INSERT INTO lt_config_workflows
24
- (workflow_type, task_queue, default_role, invocable, description, tool_tags)
25
- VALUES
26
- ('mcpDeterministic', 'long-tail-system', 'engineer', false,
27
- 'Deterministic execution — invokes matched compiled YAML workflows with extracted inputs',
28
- '{}')
29
- ON CONFLICT (workflow_type) DO NOTHING;
30
-
31
- -- Assign roles
32
- INSERT INTO lt_config_roles (workflow_type, role)
33
- SELECT 'mcpQueryRouter', unnest(ARRAY['reviewer', 'engineer', 'admin'])
34
- ON CONFLICT (workflow_type, role) DO NOTHING;
35
-
36
- INSERT INTO lt_config_roles (workflow_type, role)
37
- SELECT 'mcpDeterministic', unnest(ARRAY['reviewer', 'engineer', 'admin'])
38
- ON CONFLICT (workflow_type, role) DO NOTHING;
@@ -1,29 +0,0 @@
1
- -- Workflow sets: groups of related workflows produced by plan mode.
2
-
3
- CREATE TABLE IF NOT EXISTS lt_workflow_sets (
4
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
5
- name TEXT UNIQUE NOT NULL,
6
- description TEXT,
7
- specification TEXT NOT NULL,
8
- plan JSONB NOT NULL DEFAULT '[]'::JSONB,
9
- namespaces TEXT[] NOT NULL DEFAULT '{}',
10
- status TEXT NOT NULL DEFAULT 'planning'
11
- CHECK (status IN ('planning','planned','building','deploying','completed','failed')),
12
- source_workflow_id TEXT,
13
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
14
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
15
- );
16
-
17
- CREATE OR REPLACE TRIGGER trg_lt_workflow_sets_updated_at
18
- BEFORE UPDATE ON lt_workflow_sets
19
- FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
20
-
21
- -- Extend lt_yaml_workflows with set membership columns
22
- ALTER TABLE lt_yaml_workflows ADD COLUMN IF NOT EXISTS set_id UUID
23
- REFERENCES lt_workflow_sets(id) ON DELETE SET NULL;
24
- ALTER TABLE lt_yaml_workflows ADD COLUMN IF NOT EXISTS set_role TEXT
25
- CHECK (set_role IN ('leaf', 'composition', 'router'));
26
- ALTER TABLE lt_yaml_workflows ADD COLUMN IF NOT EXISTS set_build_order INTEGER;
27
-
28
- CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_set_id
29
- ON lt_yaml_workflows (set_id) WHERE set_id IS NOT NULL;
@@ -1,37 +0,0 @@
1
- -- Split mcpTriage into router + dynamic + deterministic workflows.
2
- -- mcpTriageRouter is the new entry point (orchestrator).
3
- -- mcpTriage becomes dynamic-only (leaf).
4
- -- mcpTriageDeterministic invokes compiled YAML workflows (leaf).
5
-
6
- -- Update existing mcpTriage: no longer directly invocable (called via router)
7
- UPDATE lt_config_workflows
8
- SET invocable = false,
9
- description = 'Dynamic MCP triage — LLM agentic loop for escalation remediation'
10
- WHERE workflow_type = 'mcpTriage';
11
-
12
- -- Add mcpTriageRouter (orchestrator — the new entry point for triage)
13
- INSERT INTO lt_config_workflows
14
- (workflow_type, task_queue, default_role, invocable, description, tool_tags)
15
- VALUES
16
- ('mcpTriageRouter', 'long-tail-system', 'engineer', false,
17
- 'Triage router — discovers compiled workflows for remediation, routes to deterministic or dynamic triage',
18
- '{}')
19
- ON CONFLICT (workflow_type) DO NOTHING;
20
-
21
- -- Add mcpTriageDeterministic (leaf — invokes compiled triage workflows)
22
- INSERT INTO lt_config_workflows
23
- (workflow_type, task_queue, default_role, invocable, description, tool_tags)
24
- VALUES
25
- ('mcpTriageDeterministic', 'long-tail-system', 'engineer', false,
26
- 'Deterministic triage — invokes matched compiled workflows for escalation remediation',
27
- '{}')
28
- ON CONFLICT (workflow_type) DO NOTHING;
29
-
30
- -- Assign roles
31
- INSERT INTO lt_config_roles (workflow_type, role)
32
- SELECT 'mcpTriageRouter', unnest(ARRAY['reviewer', 'engineer', 'admin'])
33
- ON CONFLICT (workflow_type, role) DO NOTHING;
34
-
35
- INSERT INTO lt_config_roles (workflow_type, role)
36
- SELECT 'mcpTriageDeterministic', unnest(ARRAY['reviewer', 'engineer', 'admin'])
37
- ON CONFLICT (workflow_type, role) DO NOTHING;
@@ -1,7 +0,0 @@
1
- -- Enforce unique graph_topic per app_id for non-archived workflows.
2
- -- Two active/deployed/draft workflows in the same namespace must not
3
- -- share a subscribes topic — deploying them would cause routing collisions.
4
-
5
- CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_yaml_workflows_app_topic_unique
6
- ON lt_yaml_workflows (app_id, graph_topic)
7
- WHERE status != 'archived';
@@ -1,50 +0,0 @@
1
- -- ── OAuth token storage ─────────────────────────────────────────────────────
2
- -- Encrypted per-user, per-provider OAuth tokens for identity and resource OAuth.
3
-
4
- CREATE TABLE IF NOT EXISTS lt_oauth_tokens (
5
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6
- user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
7
- provider TEXT NOT NULL,
8
- label TEXT NOT NULL DEFAULT 'default',
9
- access_token_enc TEXT NOT NULL,
10
- refresh_token_enc TEXT,
11
- token_type TEXT NOT NULL DEFAULT 'bearer',
12
- scopes TEXT[] NOT NULL DEFAULT '{}',
13
- expires_at TIMESTAMPTZ,
14
- provider_user_id TEXT NOT NULL,
15
- provider_email TEXT,
16
- metadata JSONB,
17
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
18
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
19
- UNIQUE (user_id, provider, label)
20
- );
21
-
22
- -- Migration: add label column for multiple credentials per provider per user.
23
- -- Existing rows get 'default'. The unique constraint moves from (user_id, provider)
24
- -- to (user_id, provider, label).
25
- ALTER TABLE lt_oauth_tokens ADD COLUMN IF NOT EXISTS label TEXT NOT NULL DEFAULT 'default';
26
-
27
- -- Drop old unique constraint if it exists (safe no-op if already migrated)
28
- DO $$ BEGIN
29
- ALTER TABLE lt_oauth_tokens DROP CONSTRAINT IF EXISTS lt_oauth_tokens_user_id_provider_key;
30
- EXCEPTION WHEN undefined_object THEN NULL;
31
- END $$;
32
-
33
- -- Create the new composite unique constraint (idempotent via IF NOT EXISTS on index)
34
- CREATE UNIQUE INDEX IF NOT EXISTS lt_oauth_tokens_user_id_provider_label_key
35
- ON lt_oauth_tokens (user_id, provider, label);
36
-
37
- CREATE INDEX IF NOT EXISTS idx_lt_oauth_tokens_provider
38
- ON lt_oauth_tokens (provider, user_id);
39
-
40
- CREATE OR REPLACE TRIGGER trg_lt_oauth_tokens_updated_at
41
- BEFORE UPDATE ON lt_oauth_tokens
42
- FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
43
-
44
- -- ── Identity link columns on lt_users ──────────────────────────────────────
45
- ALTER TABLE lt_users ADD COLUMN IF NOT EXISTS oauth_provider TEXT;
46
- ALTER TABLE lt_users ADD COLUMN IF NOT EXISTS oauth_provider_id TEXT;
47
-
48
- CREATE INDEX IF NOT EXISTS idx_lt_users_oauth
49
- ON lt_users (oauth_provider, oauth_provider_id)
50
- WHERE oauth_provider IS NOT NULL;
@@ -1,27 +0,0 @@
1
- -- ── Service tokens for external MCP servers ─────────────────────────────────
2
- CREATE TABLE IF NOT EXISTS lt_service_tokens (
3
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
- name TEXT UNIQUE NOT NULL,
5
- token_hash TEXT NOT NULL,
6
- server_id UUID REFERENCES lt_mcp_servers(id) ON DELETE CASCADE,
7
- scopes TEXT[] NOT NULL DEFAULT '{}',
8
- expires_at TIMESTAMPTZ,
9
- last_used_at TIMESTAMPTZ,
10
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
12
- );
13
-
14
- CREATE INDEX IF NOT EXISTS idx_lt_service_tokens_server
15
- ON lt_service_tokens (server_id);
16
-
17
- CREATE OR REPLACE TRIGGER trg_lt_service_tokens_updated_at
18
- BEFORE UPDATE ON lt_service_tokens
19
- FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
20
-
21
- -- ── Audit: who initiated escalations ────────────────────────────────────────
22
- ALTER TABLE lt_escalations ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES lt_users(id);
23
- CREATE INDEX IF NOT EXISTS idx_lt_escalations_created_by
24
- ON lt_escalations (created_by) WHERE created_by IS NOT NULL;
25
-
26
- -- ── Scope declarations for MCP servers ──────────────────────────────────────
27
- ALTER TABLE lt_mcp_servers ADD COLUMN IF NOT EXISTS required_scopes TEXT[] NOT NULL DEFAULT '{}';
@@ -1,30 +0,0 @@
1
- -- 008_bot_accounts.sql
2
- -- Bot/service account support for universal IAM.
3
- -- Bots live in lt_users (account_type = 'bot') and authenticate via API keys.
4
-
5
- -- Add account_type column to lt_users to distinguish human vs bot accounts.
6
- ALTER TABLE lt_users ADD COLUMN IF NOT EXISTS account_type TEXT NOT NULL DEFAULT 'user';
7
-
8
- -- Apply check constraint (idempotent: skip if already exists).
9
- DO $$ BEGIN
10
- ALTER TABLE lt_users ADD CONSTRAINT lt_users_account_type_check
11
- CHECK (account_type IN ('user', 'bot'));
12
- EXCEPTION
13
- WHEN duplicate_object THEN NULL;
14
- END $$;
15
-
16
- -- Bot API keys — similar to lt_service_tokens but scoped to a user (bot) account.
17
- CREATE TABLE IF NOT EXISTS lt_bot_api_keys (
18
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
19
- name TEXT NOT NULL,
20
- user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
21
- key_hash TEXT NOT NULL,
22
- scopes TEXT[] NOT NULL DEFAULT '{}',
23
- expires_at TIMESTAMPTZ,
24
- last_used_at TIMESTAMPTZ,
25
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
26
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
27
- UNIQUE (user_id, name)
28
- );
29
-
30
- CREATE INDEX IF NOT EXISTS idx_bot_api_keys_user_id ON lt_bot_api_keys (user_id);
@@ -1,7 +0,0 @@
1
- -- 009_audit_trail.sql
2
- -- Add IAM audit columns to lt_tasks for identity traceability.
3
-
4
- ALTER TABLE lt_tasks ADD COLUMN IF NOT EXISTS initiated_by UUID REFERENCES lt_users(id) ON DELETE SET NULL;
5
- ALTER TABLE lt_tasks ADD COLUMN IF NOT EXISTS principal_type TEXT DEFAULT 'user';
6
-
7
- CREATE INDEX IF NOT EXISTS idx_lt_tasks_initiated_by ON lt_tasks (initiated_by) WHERE initiated_by IS NOT NULL;
@@ -1,4 +0,0 @@
1
- -- Add credential_providers column to lt_mcp_servers
2
- -- Declares which credential providers a server's tools need
3
- ALTER TABLE lt_mcp_servers
4
- ADD COLUMN IF NOT EXISTS credential_providers TEXT[] NOT NULL DEFAULT '{}';
@@ -1,37 +0,0 @@
1
- -- Ensure all system leaf workflows have config entries.
2
- -- Migrations 004/005 tried to UPDATE these but they were never seeded —
3
- -- the interceptor needs config entries to wrap workflows with lifecycle events.
4
-
5
- INSERT INTO lt_config_workflows
6
- (workflow_type, task_queue, default_role, invocable, description, tool_tags)
7
- VALUES
8
- ('mcpQuery', 'long-tail-system', 'engineer', false,
9
- 'Dynamic MCP tool orchestration — LLM agentic loop with raw MCP tools',
10
- '{}'),
11
- ('mcpTriage', 'long-tail-system', 'engineer', false,
12
- 'Dynamic MCP triage — LLM agentic loop for escalation remediation',
13
- '{}'),
14
- ('mcpWorkflowBuilder', 'long-tail-system', 'engineer', false,
15
- 'Direct pipeline builder — LLM constructs DAG from tool schemas',
16
- '{}'),
17
- ('mcpWorkflowPlanner', 'long-tail-system', 'engineer', false,
18
- 'Plan mode — decomposes specifications into multi-workflow sets',
19
- '{}')
20
- ON CONFLICT (workflow_type) DO NOTHING;
21
-
22
- -- Assign roles
23
- INSERT INTO lt_config_roles (workflow_type, role)
24
- SELECT 'mcpQuery', unnest(ARRAY['reviewer', 'engineer', 'admin'])
25
- ON CONFLICT (workflow_type, role) DO NOTHING;
26
-
27
- INSERT INTO lt_config_roles (workflow_type, role)
28
- SELECT 'mcpTriage', unnest(ARRAY['reviewer', 'engineer', 'admin'])
29
- ON CONFLICT (workflow_type, role) DO NOTHING;
30
-
31
- INSERT INTO lt_config_roles (workflow_type, role)
32
- SELECT 'mcpWorkflowBuilder', unnest(ARRAY['reviewer', 'engineer', 'admin'])
33
- ON CONFLICT (workflow_type, role) DO NOTHING;
34
-
35
- INSERT INTO lt_config_roles (workflow_type, role)
36
- SELECT 'mcpWorkflowPlanner', unnest(ARRAY['reviewer', 'engineer', 'admin'])
37
- ON CONFLICT (workflow_type, role) DO NOTHING;
@@ -1,6 +0,0 @@
1
- -- Remove delivery modality — the concept was never used for actual routing.
2
- -- Alpha cleanup: drop from config, escalations, and tasks tables.
3
-
4
- ALTER TABLE lt_config_workflows DROP COLUMN IF EXISTS default_modality;
5
- ALTER TABLE lt_escalations DROP COLUMN IF EXISTS modality;
6
- ALTER TABLE lt_tasks DROP COLUMN IF EXISTS modality;
@@ -1,9 +0,0 @@
1
- -- Add execute_as to workflow configs: proxy invocation identity.
2
- -- When set, workflows run as the named bot instead of the invoking user.
3
-
4
- ALTER TABLE lt_config_workflows ADD COLUMN IF NOT EXISTS execute_as TEXT;
5
-
6
- -- Add executing_as to tasks: records the actual executing principal
7
- -- (may differ from initiated_by when proxy invocation is used).
8
-
9
- ALTER TABLE lt_tasks ADD COLUMN IF NOT EXISTS executing_as TEXT;
@@ -1,16 +0,0 @@
1
- -- Ephemeral credential store for sensitive fields in waitFor signal payloads.
2
- -- Supports max_uses (0 = unlimited) and TTL-based expiry.
3
-
4
- CREATE TABLE IF NOT EXISTS lt_ephemeral_credentials (
5
- token UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6
- value BYTEA NOT NULL,
7
- label TEXT,
8
- max_uses INTEGER NOT NULL DEFAULT 0,
9
- use_count INTEGER NOT NULL DEFAULT 0,
10
- expires_at TIMESTAMPTZ,
11
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
12
- );
13
-
14
- CREATE INDEX IF NOT EXISTS idx_lt_ephemeral_expiry
15
- ON lt_ephemeral_credentials (expires_at)
16
- WHERE expires_at IS NOT NULL;
@@ -1,23 +0,0 @@
1
- -- Long Tail Knowledge Store
2
- -- Persistent JSONB memory for autonomous agents. Each entry lives in a domain
3
- -- (lightweight namespace) and is keyed by a human-readable string.
4
-
5
- CREATE TABLE IF NOT EXISTS lt_knowledge (
6
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
7
- domain TEXT NOT NULL,
8
- key TEXT NOT NULL,
9
- data JSONB NOT NULL DEFAULT '{}',
10
- tags TEXT[] NOT NULL DEFAULT '{}',
11
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
12
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13
- UNIQUE(domain, key)
14
- );
15
-
16
- CREATE INDEX IF NOT EXISTS idx_lt_knowledge_domain ON lt_knowledge (domain);
17
- CREATE INDEX IF NOT EXISTS idx_lt_knowledge_tags ON lt_knowledge USING GIN (tags);
18
- CREATE INDEX IF NOT EXISTS idx_lt_knowledge_data ON lt_knowledge USING GIN (data);
19
-
20
- DROP TRIGGER IF EXISTS lt_knowledge_updated_at ON lt_knowledge;
21
- CREATE TRIGGER lt_knowledge_updated_at
22
- BEFORE UPDATE ON lt_knowledge
23
- FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
@@ -1,7 +0,0 @@
1
- -- Allow 'streamable-http' as a transport type for MCP servers
2
- ALTER TABLE lt_mcp_servers
3
- DROP CONSTRAINT IF EXISTS lt_mcp_servers_transport_type_check;
4
-
5
- ALTER TABLE lt_mcp_servers
6
- ADD CONSTRAINT lt_mcp_servers_transport_type_check
7
- CHECK (transport_type IN ('stdio', 'sse', 'streamable-http'));