agentfootprint 9.1.0 → 9.2.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 (61) hide show
  1. package/README.md +15 -0
  2. package/dist/core/Agent.js +344 -18
  3. package/dist/core/Agent.js.map +1 -1
  4. package/dist/core/LLMCall.js +17 -0
  5. package/dist/core/LLMCall.js.map +1 -1
  6. package/dist/core/RunnerBase.js +22 -6
  7. package/dist/core/RunnerBase.js.map +1 -1
  8. package/dist/core/agent/AgentBuilder.js +20 -0
  9. package/dist/core/agent/AgentBuilder.js.map +1 -1
  10. package/dist/core/conversation.js +139 -0
  11. package/dist/core/conversation.js.map +1 -0
  12. package/dist/core/runCheckpoint.js +60 -2
  13. package/dist/core/runCheckpoint.js.map +1 -1
  14. package/dist/esm/core/Agent.d.ts +218 -3
  15. package/dist/esm/core/Agent.js +345 -19
  16. package/dist/esm/core/Agent.js.map +1 -1
  17. package/dist/esm/core/LLMCall.d.ts +9 -0
  18. package/dist/esm/core/LLMCall.js +17 -0
  19. package/dist/esm/core/LLMCall.js.map +1 -1
  20. package/dist/esm/core/RunnerBase.d.ts +22 -6
  21. package/dist/esm/core/RunnerBase.js +22 -6
  22. package/dist/esm/core/RunnerBase.js.map +1 -1
  23. package/dist/esm/core/agent/AgentBuilder.d.ts +5 -0
  24. package/dist/esm/core/agent/AgentBuilder.js +20 -0
  25. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  26. package/dist/esm/core/agent/types.d.ts +45 -3
  27. package/dist/esm/core/conversation.d.ts +96 -0
  28. package/dist/esm/core/conversation.js +133 -0
  29. package/dist/esm/core/conversation.js.map +1 -0
  30. package/dist/esm/core/runCheckpoint.d.ts +85 -1
  31. package/dist/esm/core/runCheckpoint.js +57 -1
  32. package/dist/esm/core/runCheckpoint.js.map +1 -1
  33. package/dist/esm/hosting/standingAgent.d.ts +6 -2
  34. package/dist/esm/hosting/standingAgent.js +36 -27
  35. package/dist/esm/hosting/standingAgent.js.map +1 -1
  36. package/dist/esm/index.d.ts +2 -1
  37. package/dist/esm/index.js +5 -1
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/hosting/standingAgent.js +36 -27
  40. package/dist/hosting/standingAgent.js.map +1 -1
  41. package/dist/index.js +9 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/types/core/Agent.d.ts +218 -3
  44. package/dist/types/core/Agent.d.ts.map +1 -1
  45. package/dist/types/core/LLMCall.d.ts +9 -0
  46. package/dist/types/core/LLMCall.d.ts.map +1 -1
  47. package/dist/types/core/RunnerBase.d.ts +22 -6
  48. package/dist/types/core/RunnerBase.d.ts.map +1 -1
  49. package/dist/types/core/agent/AgentBuilder.d.ts +5 -0
  50. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  51. package/dist/types/core/agent/types.d.ts +45 -3
  52. package/dist/types/core/agent/types.d.ts.map +1 -1
  53. package/dist/types/core/conversation.d.ts +97 -0
  54. package/dist/types/core/conversation.d.ts.map +1 -0
  55. package/dist/types/core/runCheckpoint.d.ts +85 -1
  56. package/dist/types/core/runCheckpoint.d.ts.map +1 -1
  57. package/dist/types/hosting/standingAgent.d.ts +6 -2
  58. package/dist/types/hosting/standingAgent.d.ts.map +1 -1
  59. package/dist/types/index.d.ts +2 -1
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/package.json +1 -1
package/README.md CHANGED
@@ -152,6 +152,21 @@ const agent = Agent.create({ provider, model })
152
152
 
153
153
  Same shape for `.instruction()` / `.memory()` / `.rag()` / raw `.injection()` — they're all the one primitive, `Injection = slot × trigger × cache`. [The full model ↓](#the-model--what-we-abstract)
154
154
 
155
+ ### Then keep the conversation
156
+
157
+ `run()` is **one turn**. It seeds the conversation from the message you pass and nothing else, so calling it twice gives you two conversations — right for one-shot work, and not what a chat wants. Continuing is something you name:
158
+
159
+ ```typescript
160
+ await agent.run({ message: 'Book me a table for two on Friday.' });
161
+ await agent.followUp('Make it three.'); // same conversation
162
+
163
+ // …or hand the conversation around: plain JSON, any store, any machine.
164
+ const conversation = agent.checkpoint();
165
+ await agent.run({ message: 'Make it three.', continueFrom: conversation });
166
+ ```
167
+
168
+ The conversation carries its own `identity`, so a continued turn writes its memory where the earlier turns can read it. Two things that *look* like this and are not: `identity.conversationId` is a namespace key (it scopes memory, RAG and permissions — it does not join two runs), and `.memory()` gives you **recall** in the system prompt rather than the verbatim window. `standingAgent({ agent, sessions, host })` does the whole store-and-continue dance per session for you. [Worked example, printing the wire each way →](examples/features/51-conversations.ts)
169
+
155
170
  ### Then compose control flow
156
171
 
157
172
  One agent is a `Runner`. So is every composition of agents — four control-flow primitives, and anything that runs composes into anything else:
@@ -60,6 +60,7 @@ const buildInjectionEngineSubflow_js_1 = require("../lib/injection-engine/buildI
60
60
  const pickEntry_js_1 = require("./agent/stages/pickEntry.js");
61
61
  const outputFallback_js_1 = require("./outputFallback.js");
62
62
  const runCheckpoint_js_1 = require("./runCheckpoint.js");
63
+ const conversation_js_1 = require("./conversation.js");
63
64
  const outputSchema_js_1 = require("./outputSchema.js");
64
65
  const runInput_js_1 = require("./runInput.js");
65
66
  const outputRetry_js_1 = require("./agent/stages/outputRetry.js");
@@ -283,6 +284,28 @@ class Agent extends RunnerBase_js_1.RunnerBase {
283
284
  * kept here rather than read back from the recording. Undefined after a run
284
285
  * that failed or paused. */
285
286
  lastRunAnswer;
287
+ /** The id the CONSUMER chose, or undefined when they took the default.
288
+ * `this.id` cannot answer that question — it is `'agent'` either way — and
289
+ * the stored-conversation fingerprint refuses only on ids somebody picked
290
+ * (see `AgentRunCheckpoint.agent`). */
291
+ explicitId;
292
+ /** The identity the caller gave the last run, or undefined when they gave
293
+ * none. Only an EXPLICIT identity is carried onto `checkpoint()`: the
294
+ * default is derived from a runId, and storing that would pin a whole
295
+ * conversation to the id of the one run that started it. */
296
+ lastRunIdentity;
297
+ /** The run in flight, by id — the whole of the one-turn-at-a-time guard.
298
+ * Set before the executor is built and cleared in `finally`, so a run that
299
+ * throws does not leave the agent permanently refusing. */
300
+ inFlightRunId;
301
+ /** The question a person still owes this agent an answer to. Set when a run
302
+ * ends paused, cleared by `resume()`, `abandonPause()`, or a run that
303
+ * completes. Read by the `run()` guard — see `PendingQuestionError`. */
304
+ pendingQuestion;
305
+ /** The `.selfExplain()` binding, when the builder mounted one. Held so
306
+ * `canExplain()` can answer the same question the trace tools answer, from
307
+ * the same fact. Undefined on every agent that never called `.selfExplain()`. */
308
+ selfExplainBinding;
286
309
  /**
287
310
  * Optional `ToolProvider` set via the builder's `.toolProvider()`.
288
311
  * When present, the Tools slot subflow consults it per iteration
@@ -333,6 +356,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
333
356
  this.provider = opts.provider;
334
357
  this.name = opts.name ?? 'Agent';
335
358
  this.id = opts.id ?? 'agent';
359
+ if (opts.id !== undefined)
360
+ this.explicitId = opts.id;
336
361
  this.model = opts.model;
337
362
  this.temperature = opts.temperature;
338
363
  this.maxTokens = opts.maxTokens;
@@ -508,9 +533,17 @@ class Agent extends RunnerBase_js_1.RunnerBase {
508
533
  * prop) so consumers can scrub the execution timeline post-run without
509
534
  * threading a recorder through the call site.
510
535
  *
511
- * Returns `undefined` before the first run completes. Returns the
512
- * snapshot of the most recent run on every call after — including
513
- * across multiple turns of the same Agent instance.
536
+ * `undefined` until a run has STARTED. After that it is the most recent
537
+ * run's snapshot including across multiple turns of the same instance.
538
+ *
539
+ * **It is LIVE during a run, not a completed-runs-only view.** The executor
540
+ * is assigned at run start, so calling this from an event listener, a tool,
541
+ * or any other mid-run vantage point returns the IN-FLIGHT run, partially
542
+ * filled. That is deliberate (Lens scrubs a running agent through it), and
543
+ * it is why `.selfExplain()` captures at the terminal flush instead of
544
+ * resolving through this: evidence that is supposed to describe a FINISHED
545
+ * turn cannot be read from a getter that also answers about an unfinished
546
+ * one.
514
547
  */
515
548
  getLastSnapshot() {
516
549
  return this.lastExecutor?.getSnapshot();
@@ -656,14 +689,73 @@ class Agent extends RunnerBase_js_1.RunnerBase {
656
689
  }
657
690
  return this.parseOutputAsync(out);
658
691
  }
692
+ /**
693
+ * Answer one turn.
694
+ *
695
+ * **`run()` is ONE turn, and it starts a new conversation every time.** The
696
+ * chart seeds its history from this call's `message` alone, so a second
697
+ * `run()` on the same agent does not continue the first: the model is shown
698
+ * one user message and will honestly tell your user it has not spoken to
699
+ * them before. That is deliberate — a primitive that quietly accumulated
700
+ * state across calls could never be used for one-shot work, and a hidden
701
+ * transcript is the most expensive thing an agent can carry.
702
+ *
703
+ * To continue a conversation, name it:
704
+ *
705
+ * - `agent.followUp(message)` — continue THIS agent's own last completed
706
+ * run. The one-liner, and what most callers want.
707
+ * - `run({ message, continueFrom })` — continue a conversation you are
708
+ * holding: `agent.checkpoint()` from an earlier turn, persisted anywhere
709
+ * and handed back. Works across a restart, a deploy, or a different
710
+ * machine, and is what `standingAgent` uses per session.
711
+ *
712
+ * Passing the same `identity.conversationId` to two `run()` calls does NOT
713
+ * continue anything — see {@link AgentInput.identity}. What a registered
714
+ * memory adds is *recall* of prior turns into the system-prompt slot, which
715
+ * is a different thing from the conversation itself.
716
+ *
717
+ * Two refusals guard the per-instance state this agent keeps; both replace
718
+ * behavior that used to succeed while quietly being wrong (9.2.0):
719
+ * {@link RunInFlightError} when a run is already in flight, and
720
+ * {@link PendingQuestionError} when the last run paused to ask a person
721
+ * something that nobody has answered.
722
+ *
723
+ * @example One turn, then a follow-up
724
+ * ```ts
725
+ * await agent.run({ message: 'Book me a table for two.' });
726
+ * await agent.followUp('Make it three.'); // remembers the table
727
+ * ```
728
+ */
659
729
  async run(input, options) {
660
730
  // Normalize or refuse BEFORE anything is created. A bare string is the
661
731
  // message; anything that is not a message is named and refused here
662
732
  // rather than becoming `content: undefined` inside the messages slot.
663
733
  const runInput = (0, runInput_js_1.normalizeRunInput)(input, 'Agent.run');
734
+ // Timing next, and before the executor exists: both of these refuse a call
735
+ // that would have SUCCEEDED into corrupted per-instance state or an
736
+ // orphaned human question. See ./conversation.ts for why they are throws.
737
+ this.assertNotRunning('Agent.run');
738
+ this.assertNoPendingQuestion('Agent.run');
739
+ // A conversation handed in continues through the same side channel
740
+ // `resumeOnError` uses — one restoration path, so the two doors cannot
741
+ // drift about what "continue" means. This turn's message IS appended:
742
+ // continuing a conversation adds a turn to it.
743
+ let continued;
744
+ if (runInput.continueFrom !== undefined) {
745
+ continued = (0, runCheckpoint_js_1.validateCheckpoint)(runInput.continueFrom);
746
+ this.applyContinuation(continued, 'Agent.run({ continueFrom })', runInput.message);
747
+ }
748
+ // Only an EXPLICIT identity is remembered for `checkpoint()`; see the
749
+ // field's note. `input.identity` wins over `options.identity` because the
750
+ // input bag is where a caller looks first, and both win over the stored
751
+ // conversation's — but the conversation's is used when neither was given,
752
+ // so a continued turn stays in the namespace it started in.
753
+ this.lastRunIdentity =
754
+ runInput.identity ?? options?.identity ?? (continued ? continued.identity : undefined);
664
755
  // (helper used in the catch block below — module-private function
665
756
  // declared at file end via hoisting)
666
757
  const executor = this.createExecutor(options);
758
+ this.inFlightRunId = this.currentRunContext.runId;
667
759
  // Auto-checkpoint at iteration boundaries — captures the latest
668
760
  // conversation history into a per-run tracker. On error, we
669
761
  // wrap the underlying error in `RunCheckpointError` carrying
@@ -687,7 +779,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
687
779
  const result = await executor.run({
688
780
  input: {
689
781
  message: runInput.message,
690
- ...(runInput.identity !== undefined && { identity: runInput.identity }),
782
+ ...(this.lastRunIdentity !== undefined && { identity: this.lastRunIdentity }),
691
783
  },
692
784
  // Co-engineered boundary (#16): the engine's loop-iteration limit
693
785
  // (footprintjs 9 default 1000) must never fire BELOW the agent's own
@@ -699,6 +791,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
699
791
  const finalized = this.finalizeResult(executor, result);
700
792
  if (typeof finalized === 'string')
701
793
  this.lastRunAnswer = finalized;
794
+ this.recordPendingQuestion(finalized);
702
795
  return finalized;
703
796
  }
704
797
  catch (cause) {
@@ -742,14 +835,109 @@ class Agent extends RunnerBase_js_1.RunnerBase {
742
835
  // are committed state. A crash checkpoint that carried the summary
743
836
  // in its history but not the span behind it would resume into a
744
837
  // conversation whose evidence the crash had quietly eaten.
745
- this.foldedSpansOf(this.getLastSnapshot()?.sharedState));
838
+ this.foldedSpansOf(this.getLastSnapshot()?.sharedState),
839
+ // A crash checkpoint is the same conversation carrier as
840
+ // `checkpoint()`, so it carries the same two owner facts — otherwise
841
+ // resuming after a crash would be the one path that still lost the
842
+ // identity, and the memory written after the recovery would land
843
+ // where nothing could read it.
844
+ this.conversationOwner());
746
845
  throw new runCheckpoint_js_1.RunCheckpointError(cause, checkpoint);
747
846
  }
748
847
  throw cause;
749
848
  }
750
849
  finally {
751
850
  stopTracking();
851
+ this.inFlightRunId = undefined;
852
+ // `seed` consumes the restored conversation on its way past. A run that
853
+ // died BEFORE seed never did, and a history left armed here would be
854
+ // picked up by the next run — which would then continue a conversation
855
+ // nobody asked it to. One run, one continuation.
856
+ this.pendingResumeHistory = undefined;
857
+ this.pendingResumeFolded = undefined;
858
+ }
859
+ }
860
+ /**
861
+ * Continue this agent's own last completed conversation.
862
+ *
863
+ * The one-liner for turn two and after. `run()` is one turn and starts a new
864
+ * conversation each time (see {@link Agent.run}); this reads the
865
+ * conversation off the last completed run, appends `message` as the next
866
+ * user turn, and runs from there — so the model sees what was actually said.
867
+ *
868
+ * Sugar over `run({ message, continueFrom: this.checkpoint() })` and nothing
869
+ * more: one restoration path, so the convenience cannot drift from the
870
+ * mechanism. Reach for `run({ continueFrom })` directly when the
871
+ * conversation comes from somewhere other than this instance's last run — a
872
+ * store, another process, a different machine.
873
+ *
874
+ * Refuses rather than guessing: {@link NoConversationError} when this agent
875
+ * has no completed run to continue (a "follow-up" that quietly became a
876
+ * first turn would be exactly the confusion this door exists to remove),
877
+ * and — through `run()` — {@link PendingQuestionError} when the last run
878
+ * paused to ask a person something, because a pause has its own door:
879
+ * `resume(checkpoint, decision)`.
880
+ *
881
+ * The conversation grows every turn and nothing here trims it; bounding what
882
+ * the model is shown is `.window()` / `.compaction()` / `.memory()`, not a
883
+ * silent cap on the way through.
884
+ *
885
+ * @example
886
+ * ```ts
887
+ * await agent.run({ message: 'Book me a table for two.' });
888
+ * await agent.followUp('Make it three.');
889
+ * await agent.followUp('And move it to 8pm.');
890
+ * ```
891
+ */
892
+ async followUp(message, options) {
893
+ // Refuse BEFORE the timing guards, so "there is nothing to follow up on"
894
+ // is never reported as "a run is in flight" for an agent that has simply
895
+ // not run yet.
896
+ if (this.getLastSnapshot() === undefined) {
897
+ throw new conversation_js_1.NoConversationError('Agent.followUp', 'never-run');
898
+ }
899
+ const conversation = this.checkpoint();
900
+ if (conversation === undefined || conversation.history.length === 0) {
901
+ throw new conversation_js_1.NoConversationError('Agent.followUp', 'last-run-unfinished');
752
902
  }
903
+ return this.run({ message, continueFrom: conversation }, options);
904
+ }
905
+ /**
906
+ * Drop the question this agent's last run paused to ask, on the record.
907
+ *
908
+ * A paused run is waiting on a person. Sending a different message while one
909
+ * is outstanding is refused ({@link PendingQuestionError}) because silently
910
+ * discarding a pending question makes a consent gate something any later
911
+ * message can walk around. When the question really is being dropped —
912
+ * the user changed the subject, the session timed out, the approval is no
913
+ * longer wanted — say so with this, and the next `run()` proceeds.
914
+ *
915
+ * Returns what was dropped (`undefined` when nothing was pending), so a
916
+ * caller can log or audit the abandonment rather than perform it blind. It
917
+ * does not touch the paused run's checkpoint: if you still hold that, it
918
+ * remains resumable.
919
+ */
920
+ abandonPause() {
921
+ const dropped = this.pendingQuestion;
922
+ this.pendingQuestion = undefined;
923
+ return dropped;
924
+ }
925
+ /**
926
+ * Whether {@link Agent.selfExplain}'s why-questions have a run to answer
927
+ * from right now.
928
+ *
929
+ * `false` for two different reasons, both honest: this agent was not built
930
+ * with `.selfExplain()`, or it was and no turn has completed yet (evidence
931
+ * binds at the END of a run, never to the one in flight). Either way there
932
+ * is nothing to explain, which is what a caller routing a why-question needs
933
+ * to know before it routes.
934
+ *
935
+ * The model is told the same thing by the same fact — the trace tools answer
936
+ * "No completed run is available yet" and the skill body says to say so
937
+ * plainly. This is that answer, for the program.
938
+ */
939
+ canExplain() {
940
+ return this.selfExplainBinding?.artifacts !== undefined;
753
941
  }
754
942
  /**
755
943
  * Resume an agent run from a checkpoint produced by a prior
@@ -797,14 +985,36 @@ class Agent extends RunnerBase_js_1.RunnerBase {
797
985
  */
798
986
  async resumeOnError(checkpoint, options) {
799
987
  const cp = (0, runCheckpoint_js_1.validateCheckpoint)(checkpoint);
800
- // Stash the checkpointed history on the side channel; the seed
801
- // function reads + clears it before scope.history initializes.
802
- this.pendingResumeHistory = cp.history;
803
- // And the folded spans beside it. A conversation stored before 8.2 has
804
- // none, and `undefined` is the right answer there — it means "this
805
- // conversation recorded no folds", which is exactly true.
806
- this.pendingResumeFolded = cp.folded;
807
- return this.run({ message: cp.originalInput.message }, options);
988
+ // The timing guards run HERE, not only inside `run()`, because the line
989
+ // below writes the side channel: a refusal after that write would leave a
990
+ // restored history armed and the NEXT run would silently continue somebody
991
+ // else's conversation.
992
+ this.assertNotRunning('Agent.resumeOnError');
993
+ this.assertNoPendingQuestion('Agent.resumeOnError');
994
+ // Stash the checkpointed history on the side channel; the seed function
995
+ // reads + clears it before scope.history initializes. No message is
996
+ // appended — the failing run's message is already the last user turn in
997
+ // that history, and adding it again would ask twice.
998
+ this.applyContinuation(cp, 'Agent.resumeOnError');
999
+ return this.run({
1000
+ message: cp.originalInput.message,
1001
+ // The conversation's own identity, unless this call named one. Until
1002
+ // 9.2.0 there was no way to pass either, so a recovered run silently
1003
+ // re-namespaced its memory under a fresh runId and wrote turn two
1004
+ // where turn three could not read it.
1005
+ ...(this.identityFor(options, cp) !== undefined && {
1006
+ identity: this.identityFor(options, cp),
1007
+ }),
1008
+ }, options);
1009
+ }
1010
+ /**
1011
+ * Which identity a continued turn runs under: the caller's if they named
1012
+ * one, otherwise the conversation's own.
1013
+ *
1014
+ * @internal
1015
+ */
1016
+ identityFor(options, cp) {
1017
+ return options?.identity ?? cp.identity;
808
1018
  }
809
1019
  /**
810
1020
  * Install a per-run checkpoint tracker. Listens for the agent's
@@ -871,20 +1081,37 @@ class Agent extends RunnerBase_js_1.RunnerBase {
871
1081
  const gate = (0, pause_js_1.pauseDemandsDecision)(checkpoint.pauseData);
872
1082
  if (gate && !(0, checkin_js_1.isCheckInDecision)(input))
873
1083
  throw new pause_js_1.DecisionRequiredError(gate, input);
1084
+ // The same one-turn-at-a-time guard `run()` carries: a resume writes the
1085
+ // same per-instance state a run does. Answering the question is what this
1086
+ // door is FOR, so it never checks `pendingQuestion` — it clears it.
1087
+ this.assertNotRunning('Agent.resume');
1088
+ // Settled the moment the answer is handed over, not when the resumed run
1089
+ // finishes: a resume that then FAILS must not leave the agent refusing
1090
+ // every later message on behalf of a question that has been answered.
1091
+ this.pendingQuestion = undefined;
874
1092
  this.emitPauseResume(checkpoint, input);
875
1093
  // Fresh executor — footprintjs 4.17.0+ seeds the runtime from
876
1094
  // `checkpoint.sharedState` (and nested subflow states) automatically
877
1095
  // on a fresh executor's `resume()`. No need to retain a paused
878
1096
  // executor between run/resume.
879
1097
  const executor = this.createExecutor(options);
1098
+ this.inFlightRunId = this.currentRunContext.runId;
880
1099
  this.lastRunAnswer = undefined;
881
1100
  // One run can never raise on another run's consent block.
882
1101
  this.consentOutstanding.clear();
883
- const result = await executor.resume(checkpoint, input, options);
884
- const finalized = this.finalizeResult(executor, result);
885
- if (typeof finalized === 'string')
886
- this.lastRunAnswer = finalized;
887
- return finalized;
1102
+ try {
1103
+ const result = await executor.resume(checkpoint, input, options);
1104
+ const finalized = this.finalizeResult(executor, result);
1105
+ if (typeof finalized === 'string')
1106
+ this.lastRunAnswer = finalized;
1107
+ // The question this resume answered is settled; a resume that paused
1108
+ // AGAIN has asked a new one, and that one is outstanding from here.
1109
+ this.recordPendingQuestion(finalized);
1110
+ return finalized;
1111
+ }
1112
+ finally {
1113
+ this.inFlightRunId = undefined;
1114
+ }
888
1115
  }
889
1116
  /**
890
1117
  * The conversation this agent's LAST completed run leaves behind, packed as
@@ -939,6 +1166,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
939
1166
  history.push({ role: 'assistant', content: this.lastRunAnswer });
940
1167
  }
941
1168
  const folded = this.foldedSpansOf(state);
1169
+ const owner = this.conversationOwner();
942
1170
  return {
943
1171
  version: 1,
944
1172
  runId: this.currentRunContext.runId,
@@ -950,6 +1178,9 @@ class Agent extends RunnerBase_js_1.RunnerBase {
950
1178
  // usually empty reads like "no folds were retained", which is a
951
1179
  // different claim from "there were no folds".
952
1180
  ...(folded !== undefined && { folded }),
1181
+ // Who it was for and who ran it (9.2.0) — both absent unless chosen.
1182
+ ...(owner.identity !== undefined && { identity: owner.identity }),
1183
+ ...(owner.agentId !== undefined && { agent: { id: owner.agentId } }),
953
1184
  };
954
1185
  }
955
1186
  /**
@@ -969,6 +1200,101 @@ class Agent extends RunnerBase_js_1.RunnerBase {
969
1200
  return undefined;
970
1201
  return structuredClone(spans);
971
1202
  }
1203
+ /**
1204
+ * The two owner facts every conversation carrier stamps — who the run was
1205
+ * for, and which agent ran it (9.2.0).
1206
+ *
1207
+ * One reader for `checkpoint()` and the crash checkpoint, the same rule
1208
+ * `foldedSpansOf` follows: a fact kept on one carrier and lost on the other
1209
+ * is worse than a fact kept on neither. Both are absent unless the caller
1210
+ * chose them, which is what keeps the fingerprint refusal narrow and the
1211
+ * default `conversationId` out of storage.
1212
+ *
1213
+ * @internal
1214
+ */
1215
+ conversationOwner() {
1216
+ return {
1217
+ ...(this.lastRunIdentity !== undefined && { identity: this.lastRunIdentity }),
1218
+ ...(this.explicitId !== undefined && { agentId: this.explicitId }),
1219
+ };
1220
+ }
1221
+ /**
1222
+ * Restore a stored conversation onto the side channel `seed` reads.
1223
+ *
1224
+ * THE one restoration path — `run({ continueFrom })` and `resumeOnError()`
1225
+ * both come through here, so the conversation door and the error door cannot
1226
+ * disagree about what continuing means. It checks the agent fingerprint,
1227
+ * restores history + folded spans, and adopts the conversation's identity so
1228
+ * the continued turn writes its memory where the earlier turns are.
1229
+ *
1230
+ * `appendMessage` is the difference between the two callers, and it is the
1231
+ * whole difference. Continuing a conversation ADDS this turn's user message
1232
+ * to the stored history; resuming after an error does NOT, because there the
1233
+ * message is already the last user turn in that history and appending it
1234
+ * would ask the same question twice.
1235
+ *
1236
+ * @internal
1237
+ */
1238
+ applyContinuation(cp, door, appendMessage) {
1239
+ (0, runCheckpoint_js_1.assertContinuable)(cp, this.explicitId, door);
1240
+ const history = cp.history;
1241
+ this.pendingResumeHistory =
1242
+ appendMessage === undefined
1243
+ ? history
1244
+ : [...history, { role: 'user', content: appendMessage }];
1245
+ // The folded spans beside it. A conversation stored before 8.2 has none,
1246
+ // and `undefined` is the right answer there — it means "this conversation
1247
+ // recorded no folds", which is exactly true.
1248
+ this.pendingResumeFolded = cp.folded;
1249
+ }
1250
+ /** One turn at a time — see `RunInFlightError`. @internal */
1251
+ assertNotRunning(door) {
1252
+ if (this.inFlightRunId !== undefined) {
1253
+ throw new conversation_js_1.RunInFlightError(door, this.id, this.inFlightRunId);
1254
+ }
1255
+ }
1256
+ /** A person's unanswered question outranks a new message — see
1257
+ * `PendingQuestionError`. @internal */
1258
+ assertNoPendingQuestion(door) {
1259
+ if (this.pendingQuestion !== undefined) {
1260
+ throw new conversation_js_1.PendingQuestionError(door, this.pendingQuestion);
1261
+ }
1262
+ }
1263
+ /**
1264
+ * Remember (or forget) the question this run ended on.
1265
+ *
1266
+ * A paused outcome sets it; anything else clears it, because a run that
1267
+ * reached an answer has no outstanding question by definition. Reads the
1268
+ * same `pauseData` fields `standingAgent.describePause` reads — the tool
1269
+ * name and question the dispatch loop stamped — and invents nothing.
1270
+ *
1271
+ * @internal
1272
+ */
1273
+ recordPendingQuestion(outcome) {
1274
+ if (typeof outcome === 'string') {
1275
+ this.pendingQuestion = undefined;
1276
+ return;
1277
+ }
1278
+ const data = outcome.pauseData;
1279
+ this.pendingQuestion = {
1280
+ ...(typeof data?.toolName === 'string' && { toolName: data.toolName }),
1281
+ ...(typeof data?.toolCallId === 'string' && { toolCallId: data.toolCallId }),
1282
+ ...(typeof data?.question === 'string' && { question: data.question }),
1283
+ };
1284
+ }
1285
+ /**
1286
+ * Hand the `.selfExplain()` binding to the agent that owns it.
1287
+ *
1288
+ * Called once by `AgentBuilder.build()`, immediately after `bindTo`. The
1289
+ * binding stays the tool provider's to read; the Agent holds it only so
1290
+ * `canExplain()` answers from the same fact the trace tools answer from,
1291
+ * rather than from a second guess about whether a run has completed.
1292
+ *
1293
+ * @internal
1294
+ */
1295
+ bindSelfExplain(binding) {
1296
+ this.selfExplainBinding = binding;
1297
+ }
972
1298
  /**
973
1299
  * Refuse, at run start, any declared messages-slot role this provider
974
1300
  * cannot carry inside its message list (7.21, D2).