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.
- package/README.md +15 -0
- package/dist/core/Agent.js +344 -18
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/LLMCall.js +17 -0
- package/dist/core/LLMCall.js.map +1 -1
- package/dist/core/RunnerBase.js +22 -6
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +20 -0
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/conversation.js +139 -0
- package/dist/core/conversation.js.map +1 -0
- package/dist/core/runCheckpoint.js +60 -2
- package/dist/core/runCheckpoint.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +218 -3
- package/dist/esm/core/Agent.js +345 -19
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/LLMCall.d.ts +9 -0
- package/dist/esm/core/LLMCall.js +17 -0
- package/dist/esm/core/LLMCall.js.map +1 -1
- package/dist/esm/core/RunnerBase.d.ts +22 -6
- package/dist/esm/core/RunnerBase.js +22 -6
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +5 -0
- package/dist/esm/core/agent/AgentBuilder.js +20 -0
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +45 -3
- package/dist/esm/core/conversation.d.ts +96 -0
- package/dist/esm/core/conversation.js +133 -0
- package/dist/esm/core/conversation.js.map +1 -0
- package/dist/esm/core/runCheckpoint.d.ts +85 -1
- package/dist/esm/core/runCheckpoint.js +57 -1
- package/dist/esm/core/runCheckpoint.js.map +1 -1
- package/dist/esm/hosting/standingAgent.d.ts +6 -2
- package/dist/esm/hosting/standingAgent.js +36 -27
- package/dist/esm/hosting/standingAgent.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/hosting/standingAgent.js +36 -27
- package/dist/hosting/standingAgent.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/types/core/Agent.d.ts +218 -3
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/LLMCall.d.ts +9 -0
- package/dist/types/core/LLMCall.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +22 -6
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +5 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +45 -3
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/conversation.d.ts +97 -0
- package/dist/types/core/conversation.d.ts.map +1 -0
- package/dist/types/core/runCheckpoint.d.ts +85 -1
- package/dist/types/core/runCheckpoint.d.ts.map +1 -1
- package/dist/types/hosting/standingAgent.d.ts +6 -2
- package/dist/types/hosting/standingAgent.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- 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:
|
package/dist/core/Agent.js
CHANGED
|
@@ -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
|
-
*
|
|
512
|
-
* snapshot
|
|
513
|
-
*
|
|
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
|
-
...(
|
|
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
|
-
//
|
|
801
|
-
//
|
|
802
|
-
|
|
803
|
-
//
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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).
|