@desplega.ai/agent-swarm 1.74.3 → 1.75.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 (60) hide show
  1. package/openapi.json +282 -1
  2. package/package.json +1 -1
  3. package/src/be/db.ts +36 -0
  4. package/src/be/memory/edges-store.ts +69 -0
  5. package/src/be/memory/providers/sqlite-store.ts +4 -0
  6. package/src/be/memory/raters/explicit-self.ts +22 -0
  7. package/src/be/memory/raters/implicit-citation.ts +44 -0
  8. package/src/be/memory/raters/llm-client.ts +172 -0
  9. package/src/be/memory/raters/llm.ts +394 -0
  10. package/src/be/memory/raters/noop.ts +14 -0
  11. package/src/be/memory/raters/registry.ts +86 -0
  12. package/src/be/memory/raters/retrieval.ts +88 -0
  13. package/src/be/memory/raters/run-server-raters.ts +97 -0
  14. package/src/be/memory/raters/store.ts +228 -0
  15. package/src/be/memory/raters/types.ts +101 -0
  16. package/src/be/memory/reranker.ts +32 -2
  17. package/src/be/memory/retrieval-store.ts +95 -0
  18. package/src/be/memory/types.ts +3 -0
  19. package/src/be/migrations/051_memory_posteriors_and_retrieval.sql +67 -0
  20. package/src/be/migrations/052_memory_edges.sql +36 -0
  21. package/src/be/migrations/053_agent_waiting_for_credentials_status.sql +61 -0
  22. package/src/commands/credential-wait.ts +186 -0
  23. package/src/commands/runner.ts +54 -9
  24. package/src/hooks/hook.ts +67 -10
  25. package/src/http/agents.ts +110 -0
  26. package/src/http/core.ts +5 -0
  27. package/src/http/memory.ts +230 -1
  28. package/src/prompts/memories.ts +62 -0
  29. package/src/providers/claude-adapter.ts +17 -0
  30. package/src/providers/claude-managed-adapter.ts +24 -0
  31. package/src/providers/codex-adapter.ts +125 -69
  32. package/src/providers/codex-models.ts +25 -17
  33. package/src/providers/credentials.ts +74 -0
  34. package/src/providers/devin-adapter.ts +18 -0
  35. package/src/providers/index.ts +7 -0
  36. package/src/providers/opencode-adapter.ts +60 -0
  37. package/src/providers/pi-mono-adapter.ts +71 -0
  38. package/src/providers/types.ts +34 -0
  39. package/src/server.ts +2 -0
  40. package/src/tests/codex-adapter.test.ts +5 -4
  41. package/src/tests/credential-check.test.ts +336 -0
  42. package/src/tests/credential-status-api.test.ts +181 -0
  43. package/src/tests/credential-status-routing.test.ts +150 -0
  44. package/src/tests/credential-wait.test.ts +282 -0
  45. package/src/tests/memory-edges.test.ts +722 -0
  46. package/src/tests/memory-rate-endpoint.test.ts +330 -0
  47. package/src/tests/memory-rate-tool.test.ts +252 -0
  48. package/src/tests/memory-rater-e2e.test.ts +578 -0
  49. package/src/tests/memory-rater-implicit-citation.test.ts +304 -0
  50. package/src/tests/memory-rater-llm.test.ts +806 -0
  51. package/src/tests/memory-rater-store.test.ts +249 -0
  52. package/src/tests/memory-reranker.test.ts +161 -2
  53. package/src/tests/mocks/mock-llm-rater-client.ts +35 -0
  54. package/src/tests/run-server-raters.test.ts +291 -0
  55. package/src/tests/tool-annotations.test.ts +2 -2
  56. package/src/tools/memory-rate.ts +166 -0
  57. package/src/tools/memory-search.ts +18 -0
  58. package/src/tools/store-progress.ts +37 -0
  59. package/src/tools/tool-config.ts +1 -0
  60. package/src/types.ts +5 -1
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Swarm API",
5
- "version": "1.74.3",
5
+ "version": "1.75.0",
6
6
  "description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
7
7
  },
8
8
  "servers": [
@@ -621,6 +621,126 @@
621
621
  }
622
622
  }
623
623
  },
624
+ "/api/agents/{id}/credential-status": {
625
+ "put": {
626
+ "summary": "Worker self-report of credential readiness (Phase 3 boot loop)",
627
+ "tags": [
628
+ "Agents"
629
+ ],
630
+ "security": [
631
+ {
632
+ "bearerAuth": []
633
+ }
634
+ ],
635
+ "parameters": [
636
+ {
637
+ "schema": {
638
+ "type": "string"
639
+ },
640
+ "required": true,
641
+ "name": "id",
642
+ "in": "path"
643
+ }
644
+ ],
645
+ "requestBody": {
646
+ "content": {
647
+ "application/json": {
648
+ "schema": {
649
+ "type": "object",
650
+ "properties": {
651
+ "ready": {
652
+ "type": "boolean"
653
+ },
654
+ "missing": {
655
+ "type": [
656
+ "array",
657
+ "null"
658
+ ],
659
+ "items": {
660
+ "type": "string"
661
+ }
662
+ }
663
+ },
664
+ "required": [
665
+ "ready"
666
+ ]
667
+ }
668
+ }
669
+ }
670
+ },
671
+ "responses": {
672
+ "200": {
673
+ "description": "State updated; returns the agent row."
674
+ },
675
+ "404": {
676
+ "description": "Agent not found"
677
+ }
678
+ }
679
+ },
680
+ "get": {
681
+ "summary": "Single-agent credential-status snapshot for the dashboard",
682
+ "tags": [
683
+ "Agents"
684
+ ],
685
+ "security": [
686
+ {
687
+ "bearerAuth": []
688
+ }
689
+ ],
690
+ "parameters": [
691
+ {
692
+ "schema": {
693
+ "type": "string"
694
+ },
695
+ "required": true,
696
+ "name": "id",
697
+ "in": "path"
698
+ }
699
+ ],
700
+ "responses": {
701
+ "200": {
702
+ "description": "Credential status payload"
703
+ },
704
+ "404": {
705
+ "description": "Agent not found"
706
+ }
707
+ }
708
+ }
709
+ },
710
+ "/api/agents/credential-status": {
711
+ "get": {
712
+ "summary": "Bulk credential-status across all agents (powers the dashboard)",
713
+ "tags": [
714
+ "Agents"
715
+ ],
716
+ "security": [
717
+ {
718
+ "bearerAuth": []
719
+ }
720
+ ],
721
+ "parameters": [
722
+ {
723
+ "schema": {
724
+ "type": "string",
725
+ "enum": [
726
+ "idle",
727
+ "busy",
728
+ "offline",
729
+ "waiting_for_credentials"
730
+ ]
731
+ },
732
+ "required": false,
733
+ "name": "status",
734
+ "in": "query"
735
+ }
736
+ ],
737
+ "responses": {
738
+ "200": {
739
+ "description": "List of {agentId, status, missing[], lastCheckedAt}"
740
+ }
741
+ }
742
+ }
743
+ },
624
744
  "/api/approval-requests": {
625
745
  "post": {
626
746
  "summary": "Create a new approval request",
@@ -3031,6 +3151,167 @@
3031
3151
  }
3032
3152
  }
3033
3153
  },
3154
+ "/api/memory/rate": {
3155
+ "post": {
3156
+ "summary": "Submit RatingEvents to update memory usefulness posteriors",
3157
+ "tags": [
3158
+ "Memory"
3159
+ ],
3160
+ "security": [
3161
+ {
3162
+ "bearerAuth": []
3163
+ }
3164
+ ],
3165
+ "requestBody": {
3166
+ "content": {
3167
+ "application/json": {
3168
+ "schema": {
3169
+ "type": "object",
3170
+ "properties": {
3171
+ "events": {
3172
+ "type": "array",
3173
+ "items": {
3174
+ "type": "object",
3175
+ "properties": {
3176
+ "memoryId": {
3177
+ "type": "string",
3178
+ "minLength": 1
3179
+ },
3180
+ "signal": {
3181
+ "type": "number",
3182
+ "minimum": -1,
3183
+ "maximum": 1
3184
+ },
3185
+ "weight": {
3186
+ "type": "number",
3187
+ "minimum": 0,
3188
+ "maximum": 1
3189
+ },
3190
+ "source": {
3191
+ "type": "string",
3192
+ "enum": [
3193
+ "llm",
3194
+ "explicit-self"
3195
+ ]
3196
+ },
3197
+ "reasoning": {
3198
+ "type": "string",
3199
+ "maxLength": 500
3200
+ },
3201
+ "taskId": {
3202
+ "type": "string",
3203
+ "format": "uuid"
3204
+ },
3205
+ "referencesSource": {
3206
+ "type": "string",
3207
+ "minLength": 1,
3208
+ "maxLength": 512,
3209
+ "description": "Optional external source ID this memory references. Free-form string, convention \"<source>:<identifier>\" (e.g. \"github:owner/repo#N\", \"linear:KEY-N\", \"customer:<slug>\", \"slack:<channel>:<ts>\", \"agentmail:<thread-id>\"). Pick any prefix that fits — no closed enum. When present, an edge from this memory to the external source is created/updated."
3210
+ }
3211
+ },
3212
+ "required": [
3213
+ "memoryId",
3214
+ "signal",
3215
+ "weight",
3216
+ "source"
3217
+ ]
3218
+ },
3219
+ "minItems": 1,
3220
+ "maxItems": 50
3221
+ }
3222
+ },
3223
+ "required": [
3224
+ "events"
3225
+ ]
3226
+ }
3227
+ }
3228
+ }
3229
+ },
3230
+ "responses": {
3231
+ "200": {
3232
+ "description": "Ratings applied; per-event rejections returned in body"
3233
+ },
3234
+ "400": {
3235
+ "description": "Validation error or explicit-self R6 spam-guard rejection"
3236
+ },
3237
+ "409": {
3238
+ "description": "Duplicate explicit-self rating for (taskId, memoryId)"
3239
+ }
3240
+ }
3241
+ }
3242
+ },
3243
+ "/api/memory/retrievals": {
3244
+ "get": {
3245
+ "summary": "List memories retrieved for a task or session (rater input)",
3246
+ "tags": [
3247
+ "Memory"
3248
+ ],
3249
+ "security": [
3250
+ {
3251
+ "bearerAuth": []
3252
+ }
3253
+ ],
3254
+ "parameters": [
3255
+ {
3256
+ "schema": {
3257
+ "type": "string",
3258
+ "format": "uuid"
3259
+ },
3260
+ "required": false,
3261
+ "name": "taskId",
3262
+ "in": "query"
3263
+ },
3264
+ {
3265
+ "schema": {
3266
+ "type": "string"
3267
+ },
3268
+ "required": false,
3269
+ "name": "sessionId",
3270
+ "in": "query"
3271
+ }
3272
+ ],
3273
+ "responses": {
3274
+ "200": {
3275
+ "description": "Retrieval rows joined with agent_memory"
3276
+ },
3277
+ "400": {
3278
+ "description": "Missing taskId/sessionId or X-Agent-ID"
3279
+ }
3280
+ }
3281
+ }
3282
+ },
3283
+ "/api/memory/edges": {
3284
+ "get": {
3285
+ "summary": "List references-source edges for a memory",
3286
+ "tags": [
3287
+ "Memory"
3288
+ ],
3289
+ "security": [
3290
+ {
3291
+ "bearerAuth": []
3292
+ }
3293
+ ],
3294
+ "parameters": [
3295
+ {
3296
+ "schema": {
3297
+ "type": "string",
3298
+ "minLength": 1
3299
+ },
3300
+ "required": true,
3301
+ "name": "memoryId",
3302
+ "in": "query"
3303
+ }
3304
+ ],
3305
+ "responses": {
3306
+ "200": {
3307
+ "description": "Edges with computed usefulness scores"
3308
+ },
3309
+ "400": {
3310
+ "description": "Missing memoryId or X-Agent-ID"
3311
+ }
3312
+ }
3313
+ }
3314
+ },
3034
3315
  "/api/prompt-templates/resolved": {
3035
3316
  "get": {
3036
3317
  "summary": "Resolve a prompt template for a given event type and scope chain",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.74.3",
3
+ "version": "1.75.0",
4
4
  "description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
5
5
  "license": "MIT",
6
6
  "author": "desplega.sh <contact@desplega.sh>",
package/src/be/db.ts CHANGED
@@ -555,6 +555,8 @@ type AgentRow = {
555
555
  provider: string | null;
556
556
  createdAt: string;
557
557
  lastUpdatedAt: string;
558
+ /** JSON array of env-var names; populated only when status is `waiting_for_credentials`. */
559
+ credentialMissing: string | null;
558
560
  };
559
561
 
560
562
  function rowToAgent(row: AgentRow): Agent {
@@ -578,6 +580,9 @@ function rowToAgent(row: AgentRow): Agent {
578
580
  provider: (row.provider as ProviderName | null) ?? undefined,
579
581
  createdAt: row.createdAt,
580
582
  lastUpdatedAt: row.lastUpdatedAt,
583
+ credentialMissing: row.credentialMissing
584
+ ? (JSON.parse(row.credentialMissing) as string[])
585
+ : null,
581
586
  };
582
587
  }
583
588
 
@@ -596,9 +601,36 @@ export const agentQueries = {
596
601
  "UPDATE agents SET status = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *",
597
602
  ),
598
603
 
604
+ updateCredentialState: () =>
605
+ getDb().prepare<AgentRow, [AgentStatus, string | null, string]>(
606
+ "UPDATE agents SET status = ?, credentialMissing = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *",
607
+ ),
608
+
599
609
  delete: () => getDb().prepare<null, [string]>("DELETE FROM agents WHERE id = ?"),
600
610
  };
601
611
 
612
+ /**
613
+ * Phase 3 of the worker credential safe-loop plan.
614
+ *
615
+ * `ready=true` clears the waiting state — the agent transitions to `idle`
616
+ * and the dispatcher will start handing it tasks again.
617
+ *
618
+ * `ready=false` parks the agent on `waiting_for_credentials` with the env-var
619
+ * names it's blocked on. The capacity dispatch query already filters
620
+ * `status === 'idle'` so the new value is implicitly excluded with no other
621
+ * code change.
622
+ */
623
+ export function updateAgentCredentialState(
624
+ agentId: string,
625
+ ready: boolean,
626
+ missing: string[] | null,
627
+ ): Agent | null {
628
+ const status: AgentStatus = ready ? "idle" : "waiting_for_credentials";
629
+ const missingJson = ready ? null : missing && missing.length > 0 ? JSON.stringify(missing) : null;
630
+ const row = agentQueries.updateCredentialState().get(status, missingJson, agentId);
631
+ return row ? rowToAgent(row) : null;
632
+ }
633
+
602
634
  export function createAgent(
603
635
  agent: Omit<Agent, "id" | "createdAt" | "lastUpdatedAt"> & { id?: string },
604
636
  ): Agent {
@@ -774,6 +806,10 @@ export function getRemainingCapacity(agentId: string): number {
774
806
  export function updateAgentStatusFromCapacity(agentId: string): void {
775
807
  const agent = getAgentById(agentId);
776
808
  if (!agent || agent.status === "offline") return;
809
+ // `waiting_for_credentials` is owned by the worker's credential-wait
810
+ // tick — task-completion shouldn't accidentally promote a blocked agent
811
+ // back to idle.
812
+ if (agent.status === "waiting_for_credentials") return;
777
813
 
778
814
  const activeCount = getActiveTaskCount(agentId);
779
815
  const newStatus = activeCount > 0 ? "busy" : "idle";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Read-side query helpers for the `agent_memory_edge` table.
3
+ *
4
+ * Plan: thoughts/taras/plans/2026-05-05-memory-rater-v1.5/step-6.md §7
5
+ *
6
+ * The write path lives in `src/be/memory/raters/store.ts` (`applyRating`
7
+ * UPSERTs the edge atomically with the memory's posterior update). This
8
+ * module surfaces reads to the GET `/api/memory/edges` endpoint that powers
9
+ * the homepage demo ("this memory references PR #377").
10
+ *
11
+ * Server-side only.
12
+ */
13
+ import { getDb } from "@/be/db";
14
+
15
+ const USEFULNESS_FLOOR = 1.0;
16
+ const USEFULNESS_CEILING = 2.0;
17
+
18
+ export type MemoryEdgeRow = {
19
+ to: string;
20
+ type: "references-source";
21
+ alpha: number;
22
+ beta: number;
23
+ /** clamp(2 * α/(α+β), 1.0, 2.0) — same formula as the memory reranker. */
24
+ usefulness: number;
25
+ createdAt: string;
26
+ };
27
+
28
+ /**
29
+ * List edges for a memory, with defence-in-depth: the joined `agent_memory`
30
+ * row must either be swarm-scope or owned by the requesting agent. Returns
31
+ * `[]` when the memory does not exist or is not visible to the agent — same
32
+ * shape as a memory with no edges, since neither case has anything useful
33
+ * to surface to the caller.
34
+ */
35
+ export function listEdgesForAgent(agentId: string, memoryId: string): MemoryEdgeRow[] {
36
+ const db = getDb();
37
+ const memory = db
38
+ .prepare<{ scope: string; agentId: string | null }, [string]>(
39
+ "SELECT scope, agentId FROM agent_memory WHERE id = ?",
40
+ )
41
+ .get(memoryId);
42
+ if (!memory) return [];
43
+ if (memory.scope !== "swarm" && memory.agentId !== agentId) return [];
44
+
45
+ const rows = db
46
+ .prepare<{ to_id: string; alpha: number; beta: number; createdAt: string }, [string]>(
47
+ `SELECT to_id, alpha, beta, createdAt
48
+ FROM agent_memory_edge
49
+ WHERE from_id = ? AND type = 'references-source'
50
+ ORDER BY createdAt DESC`,
51
+ )
52
+ .all(memoryId);
53
+
54
+ return rows.map((row) => ({
55
+ to: row.to_id,
56
+ type: "references-source" as const,
57
+ alpha: row.alpha,
58
+ beta: row.beta,
59
+ usefulness: clampUsefulness(row.alpha, row.beta),
60
+ createdAt: row.createdAt,
61
+ }));
62
+ }
63
+
64
+ function clampUsefulness(alpha: number, beta: number): number {
65
+ const denom = alpha + beta;
66
+ if (denom <= 0) return USEFULNESS_FLOOR;
67
+ const mean = alpha / denom;
68
+ return Math.max(USEFULNESS_FLOOR, Math.min(USEFULNESS_CEILING, 2 * mean));
69
+ }
@@ -30,6 +30,8 @@ type AgentMemoryRow = {
30
30
  expiresAt: string | null;
31
31
  accessCount: number;
32
32
  embeddingModel: string | null;
33
+ alpha: number;
34
+ beta: number;
33
35
  };
34
36
 
35
37
  function rowToAgentMemory(row: AgentMemoryRow): AgentMemory {
@@ -61,6 +63,8 @@ function rowToCandidate(row: AgentMemoryRow, similarity: number): MemoryCandidat
61
63
  accessCount: row.accessCount ?? 0,
62
64
  expiresAt: row.expiresAt ?? null,
63
65
  embeddingModel: row.embeddingModel ?? null,
66
+ alpha: row.alpha ?? 1.0,
67
+ beta: row.beta ?? 1.0,
64
68
  };
65
69
  }
66
70
 
@@ -0,0 +1,22 @@
1
+ import type { MemoryRater, RatingEvent } from "./types";
2
+
3
+ /**
4
+ * Plan: thoughts/taras/plans/2026-05-05-memory-rater-v1.5/step-5.md §3
5
+ *
6
+ * Explicit-self rater — registry sentinel only. Never auto-fires from
7
+ * `applyRating`. Its `RatingEvent`s arrive exclusively through the worker-side
8
+ * `memory_rate` MCP tool, which POSTs to `/api/memory/rate` with
9
+ * `source: "explicit-self"`.
10
+ *
11
+ * The class exists so `MEMORY_RATERS=explicit-self` can register the name —
12
+ * which (per step-5.md §5) unlocks the conditional system-prompt hint that
13
+ * teaches the agent to call `memory_rate`. Stays out of `SERVER_RATERS` so
14
+ * the store-progress hook never invokes it.
15
+ */
16
+ export class ExplicitSelfRatingRater implements MemoryRater {
17
+ readonly name = "explicit-self";
18
+
19
+ async rate(): Promise<RatingEvent[]> {
20
+ return [];
21
+ }
22
+ }
@@ -0,0 +1,44 @@
1
+ import type { MemoryRater, RatingContext, RatingEvent } from "./types";
2
+
3
+ /**
4
+ * Implicit-citation rater — pure ID-grep over `evidence`.
5
+ *
6
+ * Plan: thoughts/taras/plans/2026-05-05-memory-rater-v1.5/step-2.md §4
7
+ *
8
+ * For each `memoryId` in `ctx.retrievedMemoryIds`:
9
+ * - if `ctx.evidence` contains the literal `memoryId` → +1 weight=0.5
10
+ * (positive citation; the agent referenced the memory's id somewhere
11
+ * in the task's `session_logs`).
12
+ * - else → -1 weight=0.25 (miss; we surfaced this memory but the agent
13
+ * did not cite it. Negative signal carries less confidence per
14
+ * IR convention from research §3.A and brainstorm Q4).
15
+ *
16
+ * The framework (`applyRating` in ./store.ts) sets `event.source` from the
17
+ * rater's `name`. This rater MUST NOT populate `source` itself — `applyRating`
18
+ * rejects rater-set sources to defend against rater spoofing.
19
+ *
20
+ * Match semantics: literal substring match using `String.prototype.includes`.
21
+ * If two memory IDs share a prefix (e.g. `mem-A` is a prefix of `mem-AB`),
22
+ * citing `mem-AB` will count as a hit for both. UUIDs (the production case)
23
+ * never collide so this is benign; the unit tests lock the behaviour in.
24
+ *
25
+ * Pure / deterministic / no DB I/O.
26
+ */
27
+ export class ImplicitCitationRater implements MemoryRater {
28
+ readonly name = "implicit-citation";
29
+
30
+ async rate(ctx: RatingContext): Promise<RatingEvent[]> {
31
+ if (ctx.retrievedMemoryIds.length === 0) return [];
32
+ const evidence = ctx.evidence ?? "";
33
+
34
+ const events: RatingEvent[] = [];
35
+ for (const memoryId of ctx.retrievedMemoryIds) {
36
+ if (evidence.length > 0 && evidence.includes(memoryId)) {
37
+ events.push({ memoryId, signal: 1, weight: 0.5, source: "" });
38
+ } else {
39
+ events.push({ memoryId, signal: -1, weight: 0.25, source: "" });
40
+ }
41
+ }
42
+ return events;
43
+ }
44
+ }