@desplega.ai/agent-swarm 1.92.1 → 1.93.0

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 (87) hide show
  1. package/openapi.json +63 -3
  2. package/package.json +5 -5
  3. package/src/be/db.ts +180 -6
  4. package/src/be/memory/boot-reembed.ts +84 -0
  5. package/src/be/memory/constants.ts +42 -1
  6. package/src/be/memory/providers/openai-embedding.ts +13 -0
  7. package/src/be/memory/providers/sqlite-store.ts +75 -26
  8. package/src/be/memory/raters/llm-client.ts +12 -5
  9. package/src/be/memory/reranker.ts +35 -17
  10. package/src/be/memory/types.ts +11 -0
  11. package/src/be/migrations/088_script_runs_list_indexes.sql +10 -0
  12. package/src/be/migrations/089_harness_variant.sql +2 -0
  13. package/src/be/modelsdev-cache.json +6478 -3099
  14. package/src/be/seed-pricing.ts +1 -0
  15. package/src/be/seed-scripts/catalog/boot-triage.inline.ts +221 -0
  16. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +457 -0
  17. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +863 -0
  18. package/src/be/seed-scripts/catalog/compound-insights.ts +371 -0
  19. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +506 -0
  20. package/src/be/seed-scripts/index.ts +5 -5
  21. package/src/be/skill-sync.ts +28 -179
  22. package/src/commands/runner.ts +124 -7
  23. package/src/http/api-keys.ts +42 -0
  24. package/src/http/index.ts +9 -0
  25. package/src/http/mcp-bridge.ts +1 -1
  26. package/src/http/memory.ts +27 -24
  27. package/src/http/tasks.ts +10 -6
  28. package/src/providers/claude-adapter.ts +33 -1
  29. package/src/providers/claude-managed-adapter.ts +3 -0
  30. package/src/providers/claude-managed-models.ts +7 -0
  31. package/src/providers/codex-adapter.ts +8 -1
  32. package/src/providers/codex-models.ts +1 -0
  33. package/src/providers/codex-oauth/auth-json.ts +1 -0
  34. package/src/providers/harness-version.ts +7 -0
  35. package/src/providers/opencode-adapter.ts +11 -4
  36. package/src/providers/pi-mono-adapter.ts +12 -2
  37. package/src/providers/types.ts +2 -0
  38. package/src/scripts-runtime/egress-secrets.ts +83 -0
  39. package/src/scripts-runtime/eval-harness.ts +4 -0
  40. package/src/scripts-runtime/executors/types.ts +7 -0
  41. package/src/scripts-runtime/loader.ts +2 -0
  42. package/src/server-user.ts +2 -2
  43. package/src/slack/channel-join.ts +41 -0
  44. package/src/tasks/worker-follow-up.ts +12 -0
  45. package/src/tests/additive-buffer.test.ts +0 -1
  46. package/src/tests/api-key-tracking.test.ts +113 -0
  47. package/src/tests/approval-requests.test.ts +0 -6
  48. package/src/tests/claude-managed-setup.test.ts +0 -4
  49. package/src/tests/codex-pool.test.ts +2 -6
  50. package/src/tests/http-api-integration.test.ts +4 -6
  51. package/src/tests/memory-e2e.test.ts +6 -6
  52. package/src/tests/memory-edges.test.ts +0 -2
  53. package/src/tests/memory-rate-endpoint.test.ts +0 -2
  54. package/src/tests/memory-rater-e2e.test.ts +4 -7
  55. package/src/tests/memory-reranker.test.ts +135 -124
  56. package/src/tests/memory-store.test.ts +19 -1
  57. package/src/tests/memory.test.ts +64 -12
  58. package/src/tests/model-control.test.ts +1 -1
  59. package/src/tests/reload-config.test.ts +33 -17
  60. package/src/tests/runner-skills-refresh.test.ts +216 -46
  61. package/src/tests/script-runs-http.test.ts +7 -1
  62. package/src/tests/scripts-runtime-secret-egress.test.ts +129 -0
  63. package/src/tests/seed-scripts.test.ts +218 -1
  64. package/src/tests/session-attach.test.ts +6 -6
  65. package/src/tests/skill-fs-writer.test.ts +250 -0
  66. package/src/tests/slack-attachments-block.test.ts +0 -1
  67. package/src/tests/slack-blocks.test.ts +0 -1
  68. package/src/tests/slack-channel-join.test.ts +80 -0
  69. package/src/tests/slack-identity-resolution.test.ts +0 -1
  70. package/src/tests/structured-output.test.ts +0 -2
  71. package/src/tests/task-cascade-fail.test.ts +304 -0
  72. package/src/tests/use-dismissible-card.test.ts +0 -4
  73. package/src/tools/schedules/create-schedule.ts +2 -2
  74. package/src/tools/schedules/update-schedule.ts +1 -1
  75. package/src/tools/send-task.ts +2 -2
  76. package/src/tools/slack-post.ts +18 -15
  77. package/src/tools/slack-read.ts +9 -11
  78. package/src/tools/slack-reply.ts +18 -15
  79. package/src/tools/slack-start-thread.ts +17 -14
  80. package/src/tools/task-action.ts +2 -2
  81. package/src/types.ts +11 -0
  82. package/src/utils/context-window.ts +3 -0
  83. package/src/utils/credentials.ts +22 -2
  84. package/src/utils/skill-fs-writer.ts +220 -0
  85. package/src/utils/skills-refresh.ts +123 -40
  86. package/templates/workflows/llm-safe-release-context/config.json +13 -0
  87. package/templates/workflows/llm-safe-release-context/content.md +69 -0
@@ -1,7 +1,12 @@
1
1
  import { getDb, isSqliteVecAvailable } from "@/be/db";
2
2
  import { cosineSimilarity, deserializeEmbedding, serializeEmbedding } from "@/be/embedding";
3
3
  import type { AgentMemory, AgentMemoryScope, AgentMemorySource } from "@/types";
4
- import { EMBEDDING_DIMENSIONS, TTL_DEFAULTS } from "../constants";
4
+ import {
5
+ EMBEDDING_DIMENSIONS,
6
+ MIN_SIMILARITY,
7
+ PROTECTED_SOURCES,
8
+ TTL_DEFAULTS,
9
+ } from "../constants";
5
10
  import type {
6
11
  MemoryCandidate,
7
12
  MemoryHealth,
@@ -400,6 +405,7 @@ export class SqliteMemoryStore implements MemoryStore {
400
405
  const candidates: MemoryCandidate[] = [];
401
406
  for (const row of rows) {
402
407
  const similarity = 1 - row.distance;
408
+ if (similarity < MIN_SIMILARITY) continue;
403
409
  candidates.push(rowToCandidate(row, similarity));
404
410
  }
405
411
 
@@ -446,6 +452,7 @@ export class SqliteMemoryStore implements MemoryStore {
446
452
  const emb = deserializeEmbedding(row.embedding);
447
453
  if (emb.length !== queryEmbedding.length) continue;
448
454
  const similarity = cosineSimilarity(queryEmbedding, emb);
455
+ if (similarity < MIN_SIMILARITY) continue;
449
456
  candidates.push(rowToCandidate(row, similarity));
450
457
  }
451
458
 
@@ -481,29 +488,19 @@ export class SqliteMemoryStore implements MemoryStore {
481
488
  }
482
489
  }
483
490
 
484
- list(agentId: string, options: MemoryListOptions = {}): AgentMemory[] {
485
- const { scope = "all", limit = 20, offset = 0, isLead = false, source } = options;
486
- const db = getDb();
487
-
491
+ private buildListWhereClause(
492
+ agentId: string,
493
+ options: MemoryListOptions,
494
+ ): { whereClause: string; params: (Buffer | string | number | null)[] } {
495
+ const { scope = "all", isLead = false, ownerAgentId, source, sourcePath } = options;
488
496
  const conditions: string[] = [];
489
- const params: (string | number)[] = [];
497
+ const params: (Buffer | string | number | null)[] = [];
490
498
 
491
- if (!isLead) {
492
- if (scope === "agent") {
493
- conditions.push("agentId = ? AND scope = 'agent'");
494
- params.push(agentId);
495
- } else if (scope === "swarm") {
496
- conditions.push("scope = 'swarm'");
497
- } else {
498
- conditions.push("(agentId = ? OR scope = 'swarm')");
499
- params.push(agentId);
500
- }
501
- } else {
502
- if (scope === "agent") {
503
- conditions.push("scope = 'agent'");
504
- } else if (scope === "swarm") {
505
- conditions.push("scope = 'swarm'");
506
- }
499
+ this.addScopeConditions(conditions, params, agentId, scope, isLead);
500
+
501
+ if (ownerAgentId) {
502
+ conditions.push("agentId = ?");
503
+ params.push(ownerAgentId);
507
504
  }
508
505
 
509
506
  if (source) {
@@ -511,18 +508,70 @@ export class SqliteMemoryStore implements MemoryStore {
511
508
  params.push(source);
512
509
  }
513
510
 
514
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
515
- params.push(limit, offset);
511
+ const sourcePathNeedle = sourcePath?.trim().toLowerCase();
512
+ if (sourcePathNeedle) {
513
+ conditions.push("instr(lower(coalesce(sourcePath, '')), ?) > 0");
514
+ params.push(sourcePathNeedle);
515
+ }
516
+
517
+ return {
518
+ whereClause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
519
+ params,
520
+ };
521
+ }
522
+
523
+ list(agentId: string, options: MemoryListOptions = {}): AgentMemory[] {
524
+ const { limit = 20, offset = 0 } = options;
525
+ const db = getDb();
526
+ const { whereClause, params } = this.buildListWhereClause(agentId, options);
527
+ const queryParams = [...params, limit, offset];
516
528
 
517
529
  const rows = db
518
- .prepare<AgentMemoryRow, (string | number)[]>(
530
+ .prepare<AgentMemoryRow, (Buffer | string | number | null)[]>(
519
531
  `SELECT * FROM agent_memory ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
520
532
  )
521
- .all(...params);
533
+ .all(...queryParams);
522
534
 
523
535
  return rows.map(rowToAgentMemory);
524
536
  }
525
537
 
538
+ count(agentId: string, options: MemoryListOptions = {}): number {
539
+ const db = getDb();
540
+ const { whereClause, params } = this.buildListWhereClause(agentId, options);
541
+ const row = db
542
+ .prepare<{ count: number }, (Buffer | string | number | null)[]>(
543
+ `SELECT COUNT(*) AS count FROM agent_memory ${whereClause}`,
544
+ )
545
+ .get(...params);
546
+
547
+ return row?.count ?? 0;
548
+ }
549
+
550
+ isSourceProtected(source: AgentMemorySource): boolean {
551
+ return PROTECTED_SOURCES.has(source);
552
+ }
553
+
554
+ listForCuration(
555
+ agentId?: string,
556
+ ): { id: string; source: string; name: string; createdAt: string }[] {
557
+ const db = getDb();
558
+ const protectedList = [...PROTECTED_SOURCES].map((s) => `'${s}'`).join(",");
559
+ if (agentId) {
560
+ return db
561
+ .prepare<{ id: string; source: string; name: string; createdAt: string }, [string]>(
562
+ `SELECT id, source, name, createdAt FROM agent_memory
563
+ WHERE agentId = ? AND source NOT IN (${protectedList})`,
564
+ )
565
+ .all(agentId);
566
+ }
567
+ return db
568
+ .prepare<{ id: string; source: string; name: string; createdAt: string }, []>(
569
+ `SELECT id, source, name, createdAt FROM agent_memory
570
+ WHERE source NOT IN (${protectedList})`,
571
+ )
572
+ .all();
573
+ }
574
+
526
575
  listForReembedding(options?: { agentId?: string }): { id: string; content: string }[] {
527
576
  const db = getDb();
528
577
  if (options?.agentId) {
@@ -75,6 +75,13 @@ AGENT RESPONSE / SUMMARY:
75
75
 
76
76
  Score 0..1.`;
77
77
 
78
+ const PLACEHOLDER_PREFIX = "$";
79
+ const QUERY_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{query}`;
80
+ const MEMORY_ID_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryId}`;
81
+ const MEMORY_NAME_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryName}`;
82
+ const MEMORY_CONTENT_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryContent}`;
83
+ const RESPONSE_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{response}`;
84
+
78
85
  /**
79
86
  * `claude -p --output-format json` returns a JSON envelope of the shape
80
87
  * `{ result: string, ... }`. We parse the envelope, then JSON-parse the
@@ -83,11 +90,11 @@ Score 0..1.`;
83
90
  type ClaudeCliEnvelope = { result?: unknown };
84
91
 
85
92
  function buildPrompt(input: LlmRaterInput): string {
86
- return PROMPT_TEMPLATE.replace("${query}", input.query)
87
- .replace("${memoryId}", input.memory.id)
88
- .replace("${memoryName}", input.memory.name)
89
- .replace("${memoryContent}", input.memory.content)
90
- .replace("${response}", input.response);
93
+ return PROMPT_TEMPLATE.replace(QUERY_PLACEHOLDER, input.query)
94
+ .replace(MEMORY_ID_PLACEHOLDER, input.memory.id)
95
+ .replace(MEMORY_NAME_PLACEHOLDER, input.memory.name)
96
+ .replace(MEMORY_CONTENT_PLACEHOLDER, input.memory.content)
97
+ .replace(RESPONSE_PLACEHOLDER, input.response);
91
98
  }
92
99
 
93
100
  function parseScoreAndReasoning(raw: unknown): LlmRaterResult | null {
@@ -1,7 +1,10 @@
1
+ import type { AgentMemorySource } from "@/types";
1
2
  import {
2
3
  ACCESS_BOOST_MAX_MULTIPLIER,
3
4
  ACCESS_BOOST_RECENCY_WINDOW_HOURS,
5
+ RECENCY_DECAY_HALF_LIFE,
4
6
  RECENCY_DECAY_HALF_LIFE_DAYS,
7
+ SOURCE_QUALITY_MULTIPLIER,
5
8
  } from "./constants";
6
9
  import type { MemoryCandidate, RerankOptions } from "./types";
7
10
 
@@ -9,13 +12,16 @@ const MS_PER_DAY = 1000 * 60 * 60 * 24;
9
12
  const MS_PER_HOUR = 1000 * 60 * 60;
10
13
 
11
14
  /**
12
- * Exponential decay based on age. A memory at exactly HALF_LIFE_DAYS old
13
- * gets multiplied by 0.5. Fresh memories get ~1.0.
15
+ * Exponential decay based on age and memory source.
16
+ * Source-aware: manual memories have no decay (Infinity half-life),
17
+ * file_index = 180d, task_completion = 14d, session_summary = 7d.
14
18
  */
15
- export function recencyDecay(createdAt: string, now: Date): number {
19
+ export function recencyDecay(createdAt: string, now: Date, source?: AgentMemorySource): number {
20
+ const halfLife = source ? RECENCY_DECAY_HALF_LIFE[source] : RECENCY_DECAY_HALF_LIFE_DAYS;
21
+ if (!Number.isFinite(halfLife)) return 1.0;
16
22
  const ageDays = (now.getTime() - new Date(createdAt).getTime()) / MS_PER_DAY;
17
23
  if (ageDays <= 0) return 1.0;
18
- return 2 ** (-ageDays / RECENCY_DECAY_HALF_LIFE_DAYS);
24
+ return 2 ** (-ageDays / halfLife);
19
25
  }
20
26
 
21
27
  /**
@@ -31,6 +37,14 @@ export function accessBoost(accessedAt: string, accessCount: number, now: Date):
31
37
  return boost;
32
38
  }
33
39
 
40
+ /**
41
+ * Source-quality multiplier. Manual memories get a 1.5× boost,
42
+ * session summaries get 0.5×. Unknown sources default to 1.0.
43
+ */
44
+ export function sourceQuality(source: AgentMemorySource): number {
45
+ return SOURCE_QUALITY_MULTIPLIER[source] ?? 1.0;
46
+ }
47
+
34
48
  /**
35
49
  * Beta-Binomial usefulness factor for reranking.
36
50
  *
@@ -56,33 +70,37 @@ export function usefulness(alpha: number, beta: number): number {
56
70
  }
57
71
 
58
72
  /**
59
- * Final score combining similarity, recency decay, access boost, and
60
- * Beta-Binomial usefulness. With default Beta(1,1) and default
61
- * MEMORY_DEMOTION_FLOOR=1.0, the usefulness factor is exactly 1.0 and this
62
- * computation matches the pre-rater behaviour byte-for-byte.
63
- *
64
- * v2: optional edge-aware boost — see thoughts/taras/plans/2026-05-05-memory-rater-v1.5/root.md
73
+ * Final score combining similarity, recency decay, access boost,
74
+ * source quality, and Beta-Binomial usefulness.
65
75
  */
66
76
  export function computeScore(candidate: MemoryCandidate, now: Date): number {
67
77
  return (
68
78
  candidate.similarity *
69
- recencyDecay(candidate.createdAt, now) *
79
+ recencyDecay(candidate.createdAt, now, candidate.source) *
70
80
  accessBoost(candidate.accessedAt, candidate.accessCount, now) *
81
+ sourceQuality(candidate.source) *
71
82
  usefulness(candidate.alpha, candidate.beta)
72
83
  );
73
84
  }
74
85
 
75
86
  /**
76
- * Rerank candidates by combining similarity with recency and access signals.
77
- * Returns the top `limit` candidates sorted by final score.
87
+ * Rerank candidates by combining similarity with recency, source quality,
88
+ * and access signals. Returns the top `limit` candidates sorted by composite
89
+ * score. Preserves raw similarity in `rawSimilarity` and sets `compositeScore`.
78
90
  */
79
91
  export function rerank(candidates: MemoryCandidate[], options: RerankOptions): MemoryCandidate[] {
80
92
  const { limit, now = new Date() } = options;
81
93
 
82
- const scored = candidates.map((candidate) => ({
83
- ...candidate,
84
- similarity: computeScore(candidate, now),
85
- }));
94
+ const scored = candidates.map((candidate) => {
95
+ const rawSimilarity = candidate.similarity;
96
+ const compositeScore = computeScore(candidate, now);
97
+ return {
98
+ ...candidate,
99
+ rawSimilarity,
100
+ compositeScore,
101
+ similarity: compositeScore,
102
+ };
103
+ });
86
104
 
87
105
  scored.sort((a, b) => b.similarity - a.similarity);
88
106
  return scored.slice(0, limit);
@@ -22,6 +22,11 @@ export interface MemoryStore {
22
22
  peek(id: string): AgentMemory | null;
23
23
  search(embedding: Float32Array, agentId: string, options: MemorySearchOptions): MemoryCandidate[];
24
24
  list(agentId: string, options: MemoryListOptions): AgentMemory[];
25
+ count(agentId: string, options: MemoryListOptions): number;
26
+ isSourceProtected(source: AgentMemorySource): boolean;
27
+ listForCuration(
28
+ agentId?: string,
29
+ ): { id: string; source: string; name: string; createdAt: string }[];
25
30
  listForReembedding(options?: { agentId?: string }): { id: string; content: string }[];
26
31
  delete(id: string): boolean;
27
32
  deleteBySourcePath(sourcePath: string, agentId: string): number;
@@ -51,6 +56,10 @@ export interface MemoryInput {
51
56
 
52
57
  export interface MemoryCandidate extends AgentMemory {
53
58
  similarity: number;
59
+ /** Raw cosine similarity before reranking (preserved for diagnostics). */
60
+ rawSimilarity?: number;
61
+ /** Final composite score after reranking (recency × source × usefulness × access). */
62
+ compositeScore?: number;
54
63
  accessCount: number;
55
64
  expiresAt: string | null;
56
65
  embeddingModel: string | null;
@@ -72,7 +81,9 @@ export interface MemoryListOptions {
72
81
  limit?: number;
73
82
  offset?: number;
74
83
  isLead?: boolean;
84
+ ownerAgentId?: string;
75
85
  source?: AgentMemorySource;
86
+ sourcePath?: string;
76
87
  }
77
88
 
78
89
  export interface MemoryStats {
@@ -0,0 +1,10 @@
1
+ -- Keep script run list pages fast as historical rows accumulate.
2
+
3
+ CREATE INDEX IF NOT EXISTS idx_script_runs_startedAt
4
+ ON script_runs(startedAt DESC);
5
+
6
+ CREATE INDEX IF NOT EXISTS idx_script_runs_status_startedAt
7
+ ON script_runs(status, startedAt DESC);
8
+
9
+ CREATE INDEX IF NOT EXISTS idx_script_runs_agentId_startedAt
10
+ ON script_runs(agentId, startedAt DESC);
@@ -0,0 +1,2 @@
1
+ ALTER TABLE agent_tasks ADD COLUMN harnessVariant TEXT;
2
+ ALTER TABLE agent_tasks ADD COLUMN harnessVariantMeta TEXT;