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