@slopus/happy-agent-base 0.0.2 → 0.0.3

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 (46) hide show
  1. package/README.md +20 -635
  2. package/dist/Agent.d.ts +7 -0
  3. package/dist/Agent.d.ts.map +1 -1
  4. package/dist/Agent.js +11 -0
  5. package/dist/Agent.js.map +1 -1
  6. package/dist/AgentBase.d.ts +15 -17
  7. package/dist/AgentBase.d.ts.map +1 -1
  8. package/dist/AgentBase.js +130 -229
  9. package/dist/AgentBase.js.map +1 -1
  10. package/dist/AgentBaseHooks.d.ts +1 -5
  11. package/dist/AgentBaseHooks.d.ts.map +1 -1
  12. package/dist/AgentBasePending.d.ts +3 -8
  13. package/dist/AgentBasePending.d.ts.map +1 -1
  14. package/dist/AgentBasePending.js +4 -13
  15. package/dist/AgentBasePending.js.map +1 -1
  16. package/dist/AgentFeature.d.ts +14 -1
  17. package/dist/AgentFeature.d.ts.map +1 -1
  18. package/dist/AgentKV.d.ts +0 -5
  19. package/dist/AgentKV.d.ts.map +1 -1
  20. package/dist/AgentKV.js +0 -8
  21. package/dist/AgentKV.js.map +1 -1
  22. package/dist/AgentPersistence.d.ts +0 -30
  23. package/dist/AgentPersistence.d.ts.map +1 -1
  24. package/dist/AgentProviders.d.ts +19 -9
  25. package/dist/AgentProviders.d.ts.map +1 -1
  26. package/dist/AgentProviders.js +15 -12
  27. package/dist/AgentProviders.js.map +1 -1
  28. package/dist/AgentStorage.d.ts +20 -0
  29. package/dist/AgentStorage.d.ts.map +1 -1
  30. package/dist/AgentStorage.js +33 -0
  31. package/dist/AgentStorage.js.map +1 -1
  32. package/dist/AgentSystem.d.ts +2 -0
  33. package/dist/AgentSystem.d.ts.map +1 -1
  34. package/dist/AgentSystemLocal.d.ts +12 -11
  35. package/dist/AgentSystemLocal.d.ts.map +1 -1
  36. package/dist/AgentSystemLocal.js +186 -67
  37. package/dist/AgentSystemLocal.js.map +1 -1
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/index.js.map +1 -1
  42. package/package.json +2 -2
  43. package/dist/AgentBaseStoreLock.d.ts +0 -16
  44. package/dist/AgentBaseStoreLock.d.ts.map +0 -1
  45. package/dist/AgentBaseStoreLock.js +0 -37
  46. package/dist/AgentBaseStoreLock.js.map +0 -1
@@ -1,27 +1,30 @@
1
1
  /**
2
- * Mutable registry of happy-providers instances keyed by a caller-supplied provider ID, so the
3
- * same provider class can be registered under several IDs (for example per credential). Each
4
- * entry carries its compatibility type, which decides how far a model change can go before the
5
- * conversation must reset. Providers can be added and removed while the process runs; lookups
6
- * return the live reference or null when absent.
2
+ * Mutable registry of model-aware provider sources keyed by caller-supplied IDs. Each entry carries
3
+ * its compatibility type, which decides how far a model change can go before the conversation
4
+ * must reset.
7
5
  */
8
6
  export class AgentProviders {
9
- /** Registered providers keyed by their caller-supplied ID, alongside their compatibility type. */
7
+ /** Registered provider sources keyed by their caller-supplied ID and compatibility type. */
10
8
  #providers = new Map();
11
- /** Register a provider under `id`. Throws if that ID is already registered. */
12
- add(id, provider, type) {
9
+ /** Register a provider source under `id`. Throws if that ID is already registered. */
10
+ add(id, source, type) {
13
11
  if (this.#providers.has(id)) {
14
12
  throw new Error(`Provider "${id}" is already registered.`);
15
13
  }
16
- this.#providers.set(id, { provider, type });
14
+ this.#providers.set(id, { source, type });
17
15
  }
18
16
  /** Unregister the provider at `id`. Returns whether one was actually removed. */
19
17
  remove(id) {
20
18
  return this.#providers.delete(id);
21
19
  }
22
- /** The live provider instance registered at `id`, or null when none is registered. */
23
- get(id) {
24
- return this.#providers.get(id)?.provider ?? null;
20
+ /** Resolve the provider suitable for `model`, or null when `id` is not registered. */
21
+ async resolve(id, model) {
22
+ const entry = this.#providers.get(id);
23
+ if (entry === undefined)
24
+ return null;
25
+ return typeof entry.source === "function"
26
+ ? await entry.source({ id, model })
27
+ : entry.source;
25
28
  }
26
29
  /** The compatibility type the provider at `id` was registered with, or null when absent. */
27
30
  typeOf(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"AgentProviders.js","sourceRoot":"","sources":["../sources/AgentProviders.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IACvB,kGAAkG;IACzF,UAAU,GAAG,IAAI,GAAG,EAG1B,CAAC;IAEJ,+EAA+E;IAC/E,GAAG,CAAC,EAAU,EAAE,QAAsB,EAAE,IAAoC;QACxE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,0BAA0B,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,EAAU;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,sFAAsF;IACtF,GAAG,CAAC,EAAU;QACV,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;IACrD,CAAC;IAED,4FAA4F;IAC5F,MAAM,CAAC,EAAU;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;IACjD,CAAC;IAED,8CAA8C;IAC9C,IAAI,GAAG;QACH,OAAO,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;CACJ"}
1
+ {"version":3,"file":"AgentProviders.js","sourceRoot":"","sources":["../sources/AgentProviders.ts"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACvB,4FAA4F;IACnF,UAAU,GAAG,IAAI,GAAG,EAG1B,CAAC;IAEJ,sFAAsF;IACtF,GAAG,CAAC,EAAU,EAAE,MAA2B,EAAE,IAAoC;QAC7E,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,0BAA0B,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,EAAU;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,sFAAsF;IACtF,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,KAAyB;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,OAAO,KAAK,CAAC,MAAM,KAAK,UAAU;YACrC,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;YACnC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,4FAA4F;IAC5F,MAAM,CAAC,EAAU;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;IACjD,CAAC;IAED,8CAA8C;IAC9C,IAAI,GAAG;QACH,OAAO,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;CACJ"}
@@ -1,7 +1,21 @@
1
+ import type { Context } from "@steve.kite/stdlib";
1
2
  import type { AgentKV } from "./AgentKV.js";
2
3
  import type { AgentPersistence } from "./AgentPersistence.js";
4
+ /**
5
+ * The exclusive ownership of one durable agent store. The storage adapter must back this with
6
+ * the database's hard process-level lock rather than an in-memory convention.
7
+ */
8
+ export interface AgentStorageLock {
9
+ /** Release the hard lock after the owning AgentSystem has stopped every agent. */
10
+ release(ctx: Context): Promise<void>;
11
+ }
3
12
  /** What an `AgentStorage` is built from. */
4
13
  export interface AgentStorageOptions {
14
+ /**
15
+ * Acquire exclusive ownership of the whole store. This must fail while any other process or
16
+ * AgentSystem owns the same durable store.
17
+ */
18
+ readonly acquireLock: (ctx: Context) => Promise<AgentStorageLock>;
5
19
  /** Shared key-value storage used for state spanning all agents. */
6
20
  readonly kv: AgentKV;
7
21
  /** Produce the isolated persistence used by one agent. */
@@ -13,6 +27,12 @@ export declare class AgentStorage {
13
27
  /** Shared key-value storage used for state spanning all agents. */
14
28
  readonly kv: AgentKV;
15
29
  constructor(options: AgentStorageOptions);
30
+ /**
31
+ * Acquire exclusive ownership until the returned lock is released. The adapter's lock
32
+ * enforces this across processes; the local guard also rejects accidental reuse of this
33
+ * object.
34
+ */
35
+ acquireLock(ctx: Context): Promise<AgentStorageLock>;
16
36
  /** The isolated persistence for the given agent. */
17
37
  persistence(agentId: string): AgentPersistence;
18
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AgentStorage.d.ts","sourceRoot":"","sources":["../sources/AgentStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAC/D;AAED,gEAAgE;AAChE,qBAAa,YAAY;;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IAIrB,YAAY,OAAO,EAAE,mBAAmB,EAGvC;IAED,oDAAoD;IACpD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAE7C;CACJ"}
1
+ {"version":3,"file":"AgentStorage.d.ts","sourceRoot":"","sources":["../sources/AgentStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B,kFAAkF;IAClF,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAChC;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClE,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAC/D;AAED,gEAAgE;AAChE,qBAAa,YAAY;;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IAQrB,YAAY,OAAO,EAAE,mBAAmB,EAIvC;IAED;;;;OAIG;IACG,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmBzD;IAED,oDAAoD;IACpD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAE7C;CACJ"}
@@ -2,12 +2,45 @@
2
2
  export class AgentStorage {
3
3
  /** Shared key-value storage used for state spanning all agents. */
4
4
  kv;
5
+ /** Acquires the database-backed exclusive lock for this store. */
6
+ #acquireLock;
5
7
  /** Produces the isolated persistence used by one agent. */
6
8
  #persistence;
9
+ /** Prevents two systems from sharing even one AgentStorage instance. */
10
+ #owned = false;
7
11
  constructor(options) {
8
12
  this.kv = options.kv;
13
+ this.#acquireLock = options.acquireLock;
9
14
  this.#persistence = options.persistence;
10
15
  }
16
+ /**
17
+ * Acquire exclusive ownership until the returned lock is released. The adapter's lock
18
+ * enforces this across processes; the local guard also rejects accidental reuse of this
19
+ * object.
20
+ */
21
+ async acquireLock(ctx) {
22
+ if (this.#owned)
23
+ throw new Error("The agent store is already owned by another system.");
24
+ this.#owned = true;
25
+ let lock;
26
+ try {
27
+ lock = await this.#acquireLock(ctx);
28
+ }
29
+ catch (error) {
30
+ this.#owned = false;
31
+ throw error;
32
+ }
33
+ let released = false;
34
+ return {
35
+ release: async (releaseCtx) => {
36
+ if (released)
37
+ return;
38
+ await lock.release(releaseCtx);
39
+ released = true;
40
+ this.#owned = false;
41
+ },
42
+ };
43
+ }
11
44
  /** The isolated persistence for the given agent. */
12
45
  persistence(agentId) {
13
46
  return this.#persistence(agentId);
@@ -1 +1 @@
1
- {"version":3,"file":"AgentStorage.js","sourceRoot":"","sources":["../sources/AgentStorage.ts"],"names":[],"mappings":"AAWA,gEAAgE;AAChE,MAAM,OAAO,YAAY;IACrB,mEAAmE;IAC1D,EAAE,CAAU;IACrB,2DAA2D;IAClD,YAAY,CAAwC;IAE7D,YAAY,OAA4B;QACpC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAC5C,CAAC;IAED,oDAAoD;IACpD,WAAW,CAAC,OAAe;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;CACJ"}
1
+ {"version":3,"file":"AgentStorage.js","sourceRoot":"","sources":["../sources/AgentStorage.ts"],"names":[],"mappings":"AA2BA,gEAAgE;AAChE,MAAM,OAAO,YAAY;IACrB,mEAAmE;IAC1D,EAAE,CAAU;IACrB,kEAAkE;IACzD,YAAY,CAA8C;IACnE,2DAA2D;IAClD,YAAY,CAAwC;IAC7D,wEAAwE;IACxE,MAAM,GAAG,KAAK,CAAC;IAEf,YAAY,OAA4B;QACpC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;QACxC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,GAAY;QAC1B,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,IAAsB,CAAC;QAC3B,IAAI,CAAC;YACD,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,MAAM,KAAK,CAAC;QAChB,CAAC;QACD,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,OAAO;YACH,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;gBAC1B,IAAI,QAAQ;oBAAE,OAAO;gBACrB,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC/B,QAAQ,GAAG,IAAI,CAAC;gBAChB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACxB,CAAC;SACJ,CAAC;IACN,CAAC;IAED,oDAAoD;IACpD,WAAW,CAAC,OAAe;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;CACJ"}
@@ -23,6 +23,8 @@ export interface AgentInitialContext {
23
23
  export interface AgentSystem {
24
24
  /** The models this collection offers its agents. */
25
25
  readonly models: readonly AgentModel[];
26
+ /** Stop every agent and release this system's exclusive ownership of its durable store. */
27
+ close(ctx: Context): Promise<void>;
26
28
  /**
27
29
  * Create an agent with a new system-generated cuid2 identity and the configuration it keeps
28
30
  * for its whole life. The configuration and optional initial context are persisted before
@@ -1 +1 @@
1
- {"version":3,"file":"AgentSystem.d.ts","sourceRoot":"","sources":["../sources/AgentSystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAChC,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAChD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IACxB,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAEvC;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAEhG,oFAAoF;IACpF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErD,6FAA6F;IAC7F,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAExE,gFAAgF;IAChF,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAEvD;;;OAGG;IACH,KAAK,CACD,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,qBAAqB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,+FAA+F;IAC/F,IAAI,CACA,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,qBAAqB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,2FAA2F;IAC3F,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErF,wFAAwF;IACxF,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1F"}
1
+ {"version":3,"file":"AgentSystem.d.ts","sourceRoot":"","sources":["../sources/AgentSystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAChC,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAChD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IACxB,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAEvC,2FAA2F;IAC3F,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAEhG,oFAAoF;IACpF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErD,6FAA6F;IAC7F,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAExE,gFAAgF;IAChF,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAEvD;;;OAGG;IACH,KAAK,CACD,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,qBAAqB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,+FAA+F;IAC/F,IAAI,CACA,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,qBAAqB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,2FAA2F;IAC3F,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErF,wFAAwF;IACxF,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1F"}
@@ -29,10 +29,8 @@ export interface AgentSystemLocalOptions {
29
29
  * the identities its storage holds. Concurrent resolutions of the same ID share one load, while a
30
30
  * failed load is forgotten so a later resolution can retry.
31
31
  *
32
- * Identity is durable and shared, so more than one live collection can be working over one
33
- * storage. Everything below therefore serializes per agent rather than per collection a
34
- * collection-wide lock would make one agent's feature load block every other agent, including one
35
- * that load itself resolves — and treats storage, not memory, as the authority on who exists.
32
+ * Work serializes per agent rather than per collection: a collection-wide lock would make one
33
+ * agent's feature load block every other agent, including one that load itself resolves.
36
34
  *
37
35
  * This is the owner's handle, and some of what it offers waits for an agent to reach a point only
38
36
  * that agent's run loop can bring it to. Hand an `AgentSystemRef` to anything running inside an
@@ -49,8 +47,7 @@ export declare class AgentSystemLocal implements AgentSystem {
49
47
  * was running when the previous process ended is still owed an answer, and this is what
50
48
  * makes it happen. Every identity the storage holds is examined, and each one that owes work
51
49
  * is resolved and resumed, so by the time this returns the collection is not merely built
52
- * but running. An identity another owner is still creating is left alone, since until that
53
- * creation commits there is no agent to resume.
50
+ * but running.
54
51
  */
55
52
  static create(ctx: Context, storage: AgentStorage, config: AgentSystemLocalOptions): Promise<AgentSystemLocal>;
56
53
  /**
@@ -59,6 +56,11 @@ export declare class AgentSystemLocal implements AgentSystem {
59
56
  * the storage was left holding — building one without that is building half of it.
60
57
  */
61
58
  private constructor();
59
+ /**
60
+ * Stop every live agent, wait for operations already admitted by this owner, and only then
61
+ * release the database lock. Repeated callers join the same shutdown.
62
+ */
63
+ close(ctx: Context): Promise<void>;
62
64
  /**
63
65
  * Create an agent with the configuration it keeps for its whole life, and resolve it. The
64
66
  * identity is allocated here and nowhere else: it is a cuid2, globally unique, so a new
@@ -66,11 +68,10 @@ export declare class AgentSystemLocal implements AgentSystem {
66
68
  * The configuration is persisted before the agent runs, so every later process resolves the
67
69
  * agent exactly as it was created.
68
70
  *
69
- * Nothing here is ever undone: an agent whose features refuse to load leaves an identity
70
- * that exists, is resolvable, and will be built the next time something wants it. The
71
- * alternative writing a provisional identity and taking it back when the build fails —
72
- * has to get the taking-back right in the presence of crashes and other owners, and a
73
- * compensation that can itself fail is not a guarantee.
71
+ * Nothing here is undone: an agent whose features refuse to load leaves an identity that
72
+ * exists, is resolvable, and will be built the next time something wants it. Taking a
73
+ * provisional identity back after a failed build would add a compensation that can itself
74
+ * fail.
74
75
  */
75
76
  create(ctx: Context, config: AgentConfig, initialContext?: AgentInitialContext): Promise<Agent>;
76
77
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"AgentSystemLocal.d.ts","sourceRoot":"","sources":["../sources/AgentSystemLocal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElF,OAAO,EAA6B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAI1F,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIzE,yFAAyF;AACzF,MAAM,WAAW,uBAAuB;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC5C,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;CAC1C;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,gBAAiB,YAAW,WAAW;;IAChD,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IA8BvC;;;;;;;;;OASG;IACH,OAAa,MAAM,CACf,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,uBAAuB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAI3B;IAED;;;;OAIG;IACH,OAAO,eAQN;IAED;;;;;;;;;;;;OAYG;IACG,MAAM,CACR,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,WAAW,EACnB,cAAc,CAAC,EAAE,mBAAmB,GACrC,OAAO,CAAC,KAAK,CAAC,CAiBhB;IAED;;;;;;;;;OASG;IACG,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzD;IAoBD,6FAA6F;IACvF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAO5E;IAED;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CA0B3D;IAmFD,4CAA4C;IACtC,KAAK,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,qBAAqB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAGf;IAED,oCAAoC;IAC9B,IAAI,CACN,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,qBAAqB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAGf;IAED,2FAA2F;IACrF,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzF;IAED,wFAAwF;IAClF,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3F;CACJ"}
1
+ {"version":3,"file":"AgentSystemLocal.d.ts","sourceRoot":"","sources":["../sources/AgentSystemLocal.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { Value } from "@sinclair/typebox/value";
3
3
  import { asyncLock } from "@steve.kite/stdlib";
4
4
  import { Agent } from "./Agent.js";
5
5
  import {} from "./AgentBase.js";
6
- import { agentBaseWithStoreStill } from "./AgentBaseStoreLock.js";
6
+ import { agentId as agentIdOf } from "./AgentContexts.js";
7
7
  import { agentConfigSchema, withAgentConfig } from "./AgentConfig.js";
8
8
  import { withAgentSystem } from "./AgentSystemContext.js";
9
9
  import { AgentSystemRef } from "./AgentSystemRef.js";
@@ -12,10 +12,8 @@ import { AgentSystemRef } from "./AgentSystemRef.js";
12
12
  * the identities its storage holds. Concurrent resolutions of the same ID share one load, while a
13
13
  * failed load is forgotten so a later resolution can retry.
14
14
  *
15
- * Identity is durable and shared, so more than one live collection can be working over one
16
- * storage. Everything below therefore serializes per agent rather than per collection a
17
- * collection-wide lock would make one agent's feature load block every other agent, including one
18
- * that load itself resolves — and treats storage, not memory, as the authority on who exists.
15
+ * Work serializes per agent rather than per collection: a collection-wide lock would make one
16
+ * agent's feature load block every other agent, including one that load itself resolves.
19
17
  *
20
18
  * This is the owner's handle, and some of what it offers waits for an agent to reach a point only
21
19
  * that agent's run loop can bring it to. Hand an `AgentSystemRef` to anything running inside an
@@ -24,6 +22,8 @@ import { AgentSystemRef } from "./AgentSystemRef.js";
24
22
  export class AgentSystemLocal {
25
23
  /** The models this collection offers its agents. */
26
24
  models;
25
+ /** The lifetime context retained by this system and its storage lock. */
26
+ #ctx;
27
27
  /**
28
28
  * What this collection looks like from inside one of its agents, and the only form of it a
29
29
  * derived context ever carries.
@@ -37,6 +37,8 @@ export class AgentSystemLocal {
37
37
  #providers;
38
38
  /** The registry ID of the provider new agents are created with. */
39
39
  #provider;
40
+ /** Exclusive database-backed ownership of this collection's whole durable store. */
41
+ #storageLock;
40
42
  /** The configuration each identity was created with. */
41
43
  #configs;
42
44
  /**
@@ -51,6 +53,12 @@ export class AgentSystemLocal {
51
53
  #persistences = new Map();
52
54
  /** Per-agent locks handed out by `#lockFor`, created lazily the first time an ID is touched. */
53
55
  #locks = new Map();
56
+ /** Public operations admitted before shutdown and therefore allowed to finish. */
57
+ #admitted = new Set();
58
+ /** No agent operation is admitted until every feature has finished its beforeStart hook. */
59
+ #lifecycle = "initializing";
60
+ /** The shared shutdown, including release of the hard storage lock. */
61
+ #closePromise;
54
62
  /**
55
63
  * Bring up a collection over one storage and carry on where the last process left off.
56
64
  *
@@ -58,28 +66,109 @@ export class AgentSystemLocal {
58
66
  * was running when the previous process ended is still owed an answer, and this is what
59
67
  * makes it happen. Every identity the storage holds is examined, and each one that owes work
60
68
  * is resolved and resumed, so by the time this returns the collection is not merely built
61
- * but running. An identity another owner is still creating is left alone, since until that
62
- * creation commits there is no agent to resume.
69
+ * but running.
63
70
  */
64
71
  static async create(ctx, storage, config) {
65
- const system = new AgentSystemLocal(storage, config);
66
- await system.#start(ctx);
67
- return system;
72
+ const systemCtx = ctx;
73
+ const storageLock = await storage.acquireLock(systemCtx);
74
+ const system = new AgentSystemLocal(systemCtx, storage, storageLock, config);
75
+ try {
76
+ await system.#beforeStart(systemCtx);
77
+ const active = await system.#start(systemCtx);
78
+ system.#lifecycle = "open";
79
+ for (const agent of active)
80
+ agent.start();
81
+ await system.#afterStart(systemCtx);
82
+ return system;
83
+ }
84
+ catch (error) {
85
+ await system.close(systemCtx).catch(() => undefined);
86
+ throw error;
87
+ }
68
88
  }
69
89
  /**
70
90
  * Wire this collection to its storage, providers, and feature configuration, without reading
71
91
  * any of it. Private: a collection is brought up by `create`, which also resumes the work
72
92
  * the storage was left holding — building one without that is building half of it.
73
93
  */
74
- constructor(storage, options) {
94
+ constructor(ctx, storage, storageLock, options) {
95
+ this.#ctx = ctx;
75
96
  this.#features = options.features ?? [];
76
97
  this.#storage = storage;
98
+ this.#storageLock = storageLock;
77
99
  this.#providers = options.providers;
78
100
  this.#provider = options.provider;
79
101
  this.models = [...options.models];
80
102
  this.#configs = storage.kv.scoped("config");
81
103
  this.#sharedFeatureKV = storage.kv.scoped("features");
82
104
  }
105
+ /**
106
+ * Stop every live agent, wait for operations already admitted by this owner, and only then
107
+ * release the database lock. Repeated callers join the same shutdown.
108
+ */
109
+ async close(ctx) {
110
+ if (this.#closePromise === undefined) {
111
+ this.#lifecycle = "closing";
112
+ this.#closePromise = this.#shutdown();
113
+ }
114
+ const closing = this.#closePromise;
115
+ const caller = agentIdOf(ctx);
116
+ if (caller !== undefined && this.#agents.has(caller)) {
117
+ void closing.catch(() => undefined);
118
+ throw new Error("Closing the agent system from inside one of its own agents would wait for " +
119
+ "that agent's turn. Shutdown will finish and release the store after this " +
120
+ "caller returns.");
121
+ }
122
+ await closing;
123
+ }
124
+ /** The real shutdown barrier, which keeps the hard store lock until every agent is closed. */
125
+ async #shutdown() {
126
+ try {
127
+ while (this.#admitted.size > 0) {
128
+ await Promise.allSettled(this.#admitted);
129
+ }
130
+ const closed = [...this.#agents.values()].map((agent) => {
131
+ void agent.close().catch(() => undefined);
132
+ return agent.waitForClosed();
133
+ });
134
+ await Promise.allSettled(closed);
135
+ this.#agents.clear();
136
+ this.#persistences.clear();
137
+ this.#locks.clear();
138
+ }
139
+ finally {
140
+ try {
141
+ await this.#storageLock.release(this.#ctx);
142
+ }
143
+ finally {
144
+ this.#lifecycle = "closed";
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Admit one public operation while this system still owns the store. Shutdown rejects new
150
+ * admissions and waits for every earlier one before releasing the hard lock.
151
+ */
152
+ #admit(operation) {
153
+ if (this.#lifecycle !== "open") {
154
+ return Promise.reject(new Error(this.#lifecycle === "initializing"
155
+ ? "The agent system is not ready."
156
+ : "The agent system is closed."));
157
+ }
158
+ let running;
159
+ try {
160
+ // Begin synchronously so ownership-transfer boundaries such as create(config) copy
161
+ // caller-owned input before the caller can mutate it after receiving the promise.
162
+ running = operation();
163
+ }
164
+ catch (error) {
165
+ return Promise.reject(error);
166
+ }
167
+ const settled = running.then(() => undefined, () => undefined);
168
+ this.#admitted.add(settled);
169
+ void settled.finally(() => this.#admitted.delete(settled));
170
+ return running;
171
+ }
83
172
  /**
84
173
  * Create an agent with the configuration it keeps for its whole life, and resolve it. The
85
174
  * identity is allocated here and nowhere else: it is a cuid2, globally unique, so a new
@@ -87,29 +176,30 @@ export class AgentSystemLocal {
87
176
  * The configuration is persisted before the agent runs, so every later process resolves the
88
177
  * agent exactly as it was created.
89
178
  *
90
- * Nothing here is ever undone: an agent whose features refuse to load leaves an identity
91
- * that exists, is resolvable, and will be built the next time something wants it. The
92
- * alternative writing a provisional identity and taking it back when the build fails —
93
- * has to get the taking-back right in the presence of crashes and other owners, and a
94
- * compensation that can itself fail is not a guarantee.
179
+ * Nothing here is undone: an agent whose features refuse to load leaves an identity that
180
+ * exists, is resolvable, and will be built the next time something wants it. Taking a
181
+ * provisional identity back after a failed build would add a compensation that can itself
182
+ * fail.
95
183
  */
96
184
  async create(ctx, config, initialContext) {
97
- const agentId = createId();
98
- if (!Value.Check(agentConfigSchema, config)) {
99
- throw new Error(`The configuration for agent "${agentId}" is not valid.`);
100
- }
101
- // The caller keeps its own object, and may go on editing it. What was created is what
102
- // was passed at this moment, so storage and this agent's context both get a copy.
103
- const owned = structuredClone(config);
104
- return await this.#lockFor(agentId).runInLock(ctx, async (lockCtx) => {
105
- await this.#configs.write(lockCtx, agentId, owned);
106
- if (initialContext !== undefined) {
107
- await this.#seedInitialContext(lockCtx, agentId, initialContext.messages);
185
+ return await this.#admit(async () => {
186
+ const agentId = createId();
187
+ if (!Value.Check(agentConfigSchema, config)) {
188
+ throw new Error(`The configuration for agent "${agentId}" is not valid.`);
108
189
  }
109
- const agent = await this.#instantiate(lockCtx, agentId, owned);
110
- if (agent === undefined)
111
- throw new Error(`Agent "${agentId}" could not be built.`);
112
- return agent;
190
+ // The caller keeps its own object, and may go on editing it. What was created is what
191
+ // was passed at this moment, so storage and this agent's context both get a copy.
192
+ const owned = structuredClone(config);
193
+ return await this.#lockFor(agentId).runInLock(ctx, async (lockCtx) => {
194
+ await this.#configs.write(lockCtx, agentId, owned);
195
+ if (initialContext !== undefined) {
196
+ await this.#seedInitialContext(lockCtx, agentId, initialContext.messages);
197
+ }
198
+ const agent = await this.#instantiate(lockCtx, agentId, owned);
199
+ if (agent === undefined)
200
+ throw new Error(`Agent "${agentId}" could not be built.`);
201
+ return agent;
202
+ });
113
203
  });
114
204
  }
115
205
  /**
@@ -123,12 +213,14 @@ export class AgentSystemLocal {
123
213
  * what clears it.
124
214
  */
125
215
  async delete(ctx, agentId) {
126
- await this.#lockFor(agentId).runInLock(ctx, async (lockCtx) => {
127
- const agent = this.#agents.get(agentId);
128
- this.#agents.delete(agentId);
129
- await agent?.close();
130
- await this.#configs.delete(lockCtx, agentId);
131
- this.#persistences.delete(agentId);
216
+ await this.#admit(async () => {
217
+ await this.#lockFor(agentId).runInLock(ctx, async (lockCtx) => {
218
+ const agent = this.#agents.get(agentId);
219
+ this.#agents.delete(agentId);
220
+ await agent?.close();
221
+ await this.#configs.delete(lockCtx, agentId);
222
+ this.#persistences.delete(agentId);
223
+ });
132
224
  });
133
225
  }
134
226
  /** Install a projected conversation before the new agent is started. */
@@ -136,15 +228,19 @@ export class AgentSystemLocal {
136
228
  if (messages.length === 0)
137
229
  return;
138
230
  const persistence = this.#persistenceFor(agentId);
139
- await agentBaseWithStoreStill(ctx, persistence, (storeCtx) => persistence.transaction(storeCtx, async (txCtx) => {
231
+ await persistence.transaction(ctx, async (txCtx) => {
140
232
  await persistence.append(txCtx, {
141
233
  type: "compaction",
142
234
  messages: structuredClone(messages),
143
235
  });
144
- }));
236
+ });
145
237
  }
146
238
  /** The configuration an agent was created with, or undefined when there is no such agent. */
147
239
  async config(ctx, agentId) {
240
+ return await this.#admit(async () => await this.#config(ctx, agentId));
241
+ }
242
+ /** Read one stored configuration while its owning operation is already admitted. */
243
+ async #config(ctx, agentId) {
148
244
  const stored = await this.#configs.read(ctx, agentId);
149
245
  if (stored === undefined)
150
246
  return undefined;
@@ -158,6 +254,10 @@ export class AgentSystemLocal {
158
254
  * Concurrent resolutions of the same ID share one load.
159
255
  */
160
256
  async resolve(ctx, agentId) {
257
+ return await this.#admit(async () => await this.#resolve(ctx, agentId));
258
+ }
259
+ /** Resolve one agent while its owning public operation is already admitted. */
260
+ async #resolve(ctx, agentId) {
161
261
  const existing = this.#agents.get(agentId);
162
262
  if (existing !== undefined)
163
263
  return existing;
@@ -165,23 +265,13 @@ export class AgentSystemLocal {
165
265
  const resolved = this.#agents.get(agentId);
166
266
  if (resolved !== undefined)
167
267
  return resolved;
168
- const config = await this.config(lockCtx, agentId);
268
+ const config = await this.#config(lockCtx, agentId);
169
269
  if (config === undefined) {
170
270
  throw new Error(`Agent "${agentId}" has not been created.`);
171
271
  }
172
272
  const agent = await this.#instantiate(lockCtx, agentId, config);
173
273
  if (agent === undefined)
174
274
  throw new Error(`Agent "${agentId}" could not be built.`);
175
- // Building an agent takes as long as its features do, and identity is not this
176
- // collection's to hold still for that: another owner may have deleted the ID, or a
177
- // creation this resolution overtook may have rolled it back. Handing back an agent
178
- // for an identity that no longer exists strands its caller with a live object no
179
- // restart would ever reproduce, so the answer is checked before it is given.
180
- if ((await this.config(lockCtx, agentId)) === undefined) {
181
- this.#agents.delete(agentId);
182
- await agent.close();
183
- throw new Error(`Agent "${agentId}" was deleted while it was being resolved.`);
184
- }
185
275
  return agent;
186
276
  });
187
277
  }
@@ -189,7 +279,7 @@ export class AgentSystemLocal {
189
279
  * Build one agent and put it to work. Called with the agent's lock held, and only for an ID
190
280
  * that has no live instance yet.
191
281
  */
192
- async #instantiate(ctx, agentId, config, onlyIfActive = false) {
282
+ async #instantiate(ctx, agentId, config, onlyIfActive = false, start = true) {
193
283
  const agentCtx = withAgentConfig(withAgentSystem(ctx, this.#ref), config);
194
284
  const options = {
195
285
  id: agentId,
@@ -205,13 +295,14 @@ export class AgentSystemLocal {
205
295
  // resolves through — so the agent is loaded rather than created, and knows whether it
206
296
  // has work left before anything asks it. Bringing a collection up asks only for the
207
297
  // agents that do; anything else resolving an agent wants it whether it owes work or not.
208
- const agent = onlyIfActive
209
- ? await Agent.loadActive(agentCtx, options)
210
- : await Agent.load(agentCtx, options);
211
- if (agent === undefined)
298
+ const agent = await Agent.load(agentCtx, options);
299
+ if (onlyIfActive && !agent.active) {
300
+ await agent.close();
212
301
  return undefined;
213
- agent.start();
302
+ }
214
303
  this.#agents.set(agentId, agent);
304
+ if (start)
305
+ agent.start();
215
306
  return agent;
216
307
  }
217
308
  /**
@@ -228,15 +319,29 @@ export class AgentSystemLocal {
228
319
  */
229
320
  async #start(ctx) {
230
321
  const created = await this.#configs.list(ctx);
231
- await Promise.all(created.map(async ({ key: agentId, value }) => {
322
+ const results = await Promise.allSettled(created.map(async ({ key: agentId, value }) => {
232
323
  if (!Value.Check(agentConfigSchema, value))
233
- return;
234
- await this.#lockFor(agentId).runInLock(ctx, async (lockCtx) => {
324
+ return undefined;
325
+ return await this.#lockFor(agentId).runInLock(ctx, async (lockCtx) => {
235
326
  if (this.#agents.has(agentId))
236
- return;
237
- await this.#instantiate(lockCtx, agentId, value, true);
327
+ return undefined;
328
+ return await this.#instantiate(lockCtx, agentId, value, true, false);
238
329
  });
239
330
  }));
331
+ throwFirstStartFailure(results);
332
+ return results.flatMap((result) => result.status === "fulfilled" && result.value !== undefined ? [result.value] : []);
333
+ }
334
+ /** Initialize every feature before any active agent is restored or started. */
335
+ async #beforeStart(ctx) {
336
+ const startCtx = withAgentSystem(ctx, this.#ref);
337
+ const results = await Promise.allSettled(this.#features.map(async (feature) => await feature.beforeStart?.(startCtx, this.#ref)));
338
+ throwFirstStartFailure(results);
339
+ }
340
+ /** Notify every feature after all active agents have been restored and started. */
341
+ async #afterStart(ctx) {
342
+ const startCtx = withAgentSystem(ctx, this.#ref);
343
+ const results = await Promise.allSettled(this.#features.map(async (feature) => await feature.afterStart?.(startCtx, this.#ref)));
344
+ throwFirstStartFailure(results);
240
345
  }
241
346
  /** The durable store for one agent, created once and reused for the life of the collection. */
242
347
  #persistenceFor(agentId) {
@@ -262,21 +367,35 @@ export class AgentSystemLocal {
262
367
  }
263
368
  /** Queue a steered message for an agent. */
264
369
  async steer(ctx, agentId, message, options) {
265
- const agent = await this.resolve(ctx, agentId);
266
- await agent.steer(ctx, message, options);
370
+ await this.#admit(async () => {
371
+ const agent = await this.#resolve(ctx, agentId);
372
+ await agent.steer(ctx, message, options);
373
+ });
267
374
  }
268
375
  /** Queue a message for an agent. */
269
376
  async send(ctx, agentId, message, options) {
270
- const agent = await this.resolve(ctx, agentId);
271
- await agent.send(ctx, message, options);
377
+ await this.#admit(async () => {
378
+ const agent = await this.#resolve(ctx, agentId);
379
+ await agent.send(ctx, message, options);
380
+ });
272
381
  }
273
382
  /** Cancel an agent's active turn, leaving its queued messages durable for the next one. */
274
383
  async abort(ctx, agentId, options) {
275
- await (await this.resolve(ctx, agentId)).abort(ctx, options);
384
+ await this.#admit(async () => {
385
+ await (await this.#resolve(ctx, agentId)).abort(ctx, options);
386
+ });
276
387
  }
277
388
  /** Ask an agent for its conversation to be replaced by the provider's summary of it. */
278
389
  async compact(ctx, agentId, options) {
279
- await (await this.resolve(ctx, agentId)).compact(ctx, options);
390
+ await this.#admit(async () => {
391
+ await (await this.#resolve(ctx, agentId)).compact(ctx, options);
392
+ });
280
393
  }
281
394
  }
395
+ /** Start every feature even when one fails, then surface the first failure in feature order. */
396
+ function throwFirstStartFailure(results) {
397
+ const failure = results.find((result) => result.status === "rejected");
398
+ if (failure !== undefined)
399
+ throw failure.reason;
400
+ }
282
401
  //# sourceMappingURL=AgentSystemLocal.js.map