@stratusagent/core 0.11.4 → 0.11.5
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/dist/index.d.ts +819 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1066 -86
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -672,15 +672,75 @@ export interface ToolDescriptor {
|
|
|
672
672
|
/** Carried so providers hosting their own loop can see it too. */
|
|
673
673
|
risk?: ToolRisk;
|
|
674
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* What sort of thing a fact is. One flat bucket is why a long-lived store
|
|
677
|
+
* goes noisy: these have different useful lifetimes and different injection
|
|
678
|
+
* value, and no policy can tell them apart from prose.
|
|
679
|
+
*
|
|
680
|
+
* - `semantic` — a fact about the world.
|
|
681
|
+
* - `episodic` — a thing that happened.
|
|
682
|
+
* - `procedural` — how something is done.
|
|
683
|
+
* - `preference` — how the people it works with want things.
|
|
684
|
+
*/
|
|
685
|
+
export type MemoryEntryKind = 'semantic' | 'episodic' | 'procedural' | 'preference';
|
|
686
|
+
/** Every kind, in the order the tool description lists them. */
|
|
687
|
+
export declare const MEMORY_ENTRY_KINDS: readonly ["semantic", "episodic", "procedural", "preference"];
|
|
688
|
+
export declare const isMemoryEntryKind: (value: unknown) => value is MemoryEntryKind;
|
|
675
689
|
/**
|
|
676
690
|
* One remembered fact. Memory is keyed by agent, never by session or
|
|
677
691
|
* channel, so an agent carries the same knowledge everywhere it appears.
|
|
692
|
+
*
|
|
693
|
+
* Everything past the first four fields is optional and additive, and that
|
|
694
|
+
* is a promise rather than an accident: a JSONL line carrying only `id`,
|
|
695
|
+
* `agentId`, `content`, and `createdAt` still loads, is still recallable,
|
|
696
|
+
* and still reaches the prompt. A richer shape is the thing most likely to
|
|
697
|
+
* break the hand-edit promise, so nothing here may ever become required.
|
|
678
698
|
*/
|
|
679
699
|
export interface MemoryEntry {
|
|
680
700
|
id: string;
|
|
681
701
|
agentId: string;
|
|
682
702
|
content: string;
|
|
683
703
|
createdAt: string;
|
|
704
|
+
/** What sort of fact this is — see `MemoryEntryKind`. Absent means nobody said. */
|
|
705
|
+
kind?: MemoryEntryKind;
|
|
706
|
+
/**
|
|
707
|
+
* The entities this fact concerns, as the writer named them. This is what
|
|
708
|
+
* turns "everything I know about the deploy pipeline" into a lookup rather
|
|
709
|
+
* than a guess at query terms, and it is what the prompt's topic index is
|
|
710
|
+
* built from.
|
|
711
|
+
*
|
|
712
|
+
* It also *participates in matching*, in every store: an index block that
|
|
713
|
+
* advertised a topic the search could not find would invite exactly the
|
|
714
|
+
* query it then missed. `memoryEntryTokens` is where that is written once.
|
|
715
|
+
*/
|
|
716
|
+
about?: string[];
|
|
717
|
+
/**
|
|
718
|
+
* When the fact starts and stops being true — validity time, a different
|
|
719
|
+
* axis from `createdAt`, which is transaction time. Conflating the two is
|
|
720
|
+
* why assistants confidently report where someone used to work.
|
|
721
|
+
*
|
|
722
|
+
* Both bounds are ISO-8601 instants; `validUntil` is the moment the fact
|
|
723
|
+
* stops holding, exclusive. Outside the window an entry leaves what is
|
|
724
|
+
* true now — the pinned core, the topic index, the recency tail — and
|
|
725
|
+
* stays findable by `search`, marked. That is one rule for both bounds:
|
|
726
|
+
* a `validFrom` in the future is the same error running the other way,
|
|
727
|
+
* and an entry that is not true *yet* reaching the prompt would be
|
|
728
|
+
* presented as true now.
|
|
729
|
+
*/
|
|
730
|
+
validFrom?: string;
|
|
731
|
+
validUntil?: string;
|
|
732
|
+
/**
|
|
733
|
+
* The id of the entry this one replaces. The successor carries the
|
|
734
|
+
* retirement itself — one appended record, never a tombstone plus an
|
|
735
|
+
* entry, because the store's only atomicity is one `O_APPEND` record and
|
|
736
|
+
* stopping between two of them loses a fact.
|
|
737
|
+
*
|
|
738
|
+
* The retirement is scoped to *this* entry's validity window: a successor
|
|
739
|
+
* dated from next Monday leaves its predecessor standing until then, and
|
|
740
|
+
* an already-expired successor displaces nothing. See
|
|
741
|
+
* `supersededMemoryIdsAt`.
|
|
742
|
+
*/
|
|
743
|
+
supersedes?: string;
|
|
684
744
|
metadata?: JsonObject;
|
|
685
745
|
/**
|
|
686
746
|
* Where the fact came from — the least trusted thing the writing session
|
|
@@ -699,6 +759,21 @@ export interface MemoryEntry {
|
|
|
699
759
|
* reading the store, and travels with the entry as ordinary data.
|
|
700
760
|
*/
|
|
701
761
|
origin?: MemoryOrigin;
|
|
762
|
+
/**
|
|
763
|
+
* How often this entry has been read back, and when last — **derived, and
|
|
764
|
+
* never part of the record**. These are observations about *reading*, not
|
|
765
|
+
* facts the agent learned, so they live in whatever index a store keeps
|
|
766
|
+
* and a store that keeps none simply omits them. The consequence is exact
|
|
767
|
+
* and is the documented one: deleting the index loses your usage
|
|
768
|
+
* statistics, never your memories. Ranking may read them; nothing
|
|
769
|
+
* deletes on them.
|
|
770
|
+
*/
|
|
771
|
+
usage?: MemoryUsage;
|
|
772
|
+
}
|
|
773
|
+
/** See `MemoryEntry.usage`. Counted by the store on each `search` that returns the entry. */
|
|
774
|
+
export interface MemoryUsage {
|
|
775
|
+
recallCount: number;
|
|
776
|
+
lastRecalledAt: string;
|
|
702
777
|
}
|
|
703
778
|
export interface MemoryOrigin {
|
|
704
779
|
sessionId?: string;
|
|
@@ -737,16 +812,60 @@ export declare const MEMORY_ENTRY_MAX_BYTES = 4096;
|
|
|
737
812
|
* several cap-size entries still fit (see `MEMORY_ENTRY_MAX_BYTES`).
|
|
738
813
|
*/
|
|
739
814
|
export declare const MEMORY_READ_MAX_BYTES = 16384;
|
|
815
|
+
/**
|
|
816
|
+
* The recall tool's name, defined here because the prompt's topic index
|
|
817
|
+
* tells the agent what to call — a block advertising a tool by a name the
|
|
818
|
+
* registry does not use would be a dead pointer. `@stratusagent/agents`
|
|
819
|
+
* builds the tool itself and re-exports this as `RECALL_TOOL_NAME`.
|
|
820
|
+
*/
|
|
821
|
+
export declare const MEMORY_RECALL_TOOL_NAME = "memory.recall";
|
|
740
822
|
/** Results a `search` returns when the caller names no limit. */
|
|
741
823
|
export declare const MEMORY_RECALL_DEFAULT_LIMIT = 10;
|
|
742
824
|
/** Ceiling a `search` limit is clamped to, whatever the caller asked for. */
|
|
743
825
|
export declare const MEMORY_RECALL_MAX_LIMIT = 50;
|
|
744
|
-
/**
|
|
745
|
-
|
|
826
|
+
/**
|
|
827
|
+
* The pinned core's hard cap, in UTF-8 bytes of content. It **refuses
|
|
828
|
+
* rather than evicts**, on the same reasoning as the per-entry cap: a
|
|
829
|
+
* pinned set that silently drops its oldest member is a pin that does not
|
|
830
|
+
* mean anything.
|
|
831
|
+
*/
|
|
832
|
+
export declare const MEMORY_PINNED_MAX_BYTES = 2048;
|
|
833
|
+
/**
|
|
834
|
+
* The topic index's budget, in UTF-8 bytes of rendered block. Roughly a
|
|
835
|
+
* thousand tokens: enough to tell the agent the shape of its own store,
|
|
836
|
+
* which is what turns `memory.recall` from a guess into a targeted read.
|
|
837
|
+
*/
|
|
838
|
+
export declare const MEMORY_INDEX_MAX_BYTES = 4096;
|
|
839
|
+
/**
|
|
840
|
+
* The recency tail — deliberately smaller than the twenty entries that
|
|
841
|
+
* were the whole of injection before the pinned core and the topic index
|
|
842
|
+
* existed, so a cold store with nothing pinned and nothing indexed still
|
|
843
|
+
* behaves roughly as it did, and a warm one spends its budget on the two
|
|
844
|
+
* blocks that are correlated with what the agent knows.
|
|
845
|
+
*/
|
|
846
|
+
export declare const MEMORY_RECENCY_INJECTION_LIMIT = 8;
|
|
847
|
+
/** Byte budget for the recency tail, applied like every other bounded read. */
|
|
848
|
+
export declare const MEMORY_RECENCY_MAX_BYTES = 4096;
|
|
746
849
|
/** UTF-8 size of an entry's content — the unit both memory caps are stated in. */
|
|
747
850
|
export declare const memoryContentByteLength: (content: string) => number;
|
|
748
851
|
/** Refuses (never truncates) content over the per-entry cap: a half-stored fact is worse than a refused one. */
|
|
749
852
|
export declare const assertMemoryContentWithinCap: (content: string) => void;
|
|
853
|
+
/**
|
|
854
|
+
* How many entities one fact may be filed under, and how much room their
|
|
855
|
+
* names get. `about` is model-written and would otherwise be the one
|
|
856
|
+
* unbounded field on an entry: the per-entry cap covers `content` alone, so
|
|
857
|
+
* without this a single `memory.remember` could write a megabyte of entity
|
|
858
|
+
* names into the record and into every store's search index. The prompt's
|
|
859
|
+
* topic block has its own budget and would survive it; the record would
|
|
860
|
+
* not, and the record is the thing that has to stay hand-editable.
|
|
861
|
+
*
|
|
862
|
+
* Small on purpose. A fact about a dozen things is a fact that has not been
|
|
863
|
+
* written down properly yet.
|
|
864
|
+
*/
|
|
865
|
+
export declare const MEMORY_ABOUT_MAX_KEYS = 12;
|
|
866
|
+
export declare const MEMORY_ABOUT_MAX_BYTES = 512;
|
|
867
|
+
/** Refuses, like the content cap, and for the same reason: a half-filed fact is worse than a refused one. */
|
|
868
|
+
export declare const assertMemoryAboutWithinCap: (about: readonly string[]) => void;
|
|
750
869
|
/**
|
|
751
870
|
* The tokenizer both store implementations share: NFC-normalized,
|
|
752
871
|
* case-folded, split on anything that is not a Unicode letter or digit.
|
|
@@ -756,12 +875,72 @@ export declare const assertMemoryContentWithinCap: (content: string) => void;
|
|
|
756
875
|
*/
|
|
757
876
|
export declare const tokenizeMemoryText: (text: string) => string[];
|
|
758
877
|
/**
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
878
|
+
* An entry's `about` keys, cleaned the one way every consumer reads them:
|
|
879
|
+
* trimmed, empty ones dropped, duplicates collapsed case-insensitively
|
|
880
|
+
* with the first spelling kept. A hand-edited line whose `about` is not an
|
|
881
|
+
* array of strings contributes nothing rather than throwing — the record
|
|
882
|
+
* is the operator's to edit, and a bad key is not worth losing a fact over.
|
|
883
|
+
*/
|
|
884
|
+
export declare const memoryEntryAbout: (entry: Pick<MemoryEntry, "about">) => string[];
|
|
885
|
+
/**
|
|
886
|
+
* Everything about an entry a query is matched against: its content and its
|
|
887
|
+
* `about` keys, through the shared tokenizer.
|
|
888
|
+
*
|
|
889
|
+
* `about` participating is the point rather than an edge. An entry reading
|
|
890
|
+
* "it now uses Postgres" with `about: ["deploy pipeline"]` is listed under
|
|
891
|
+
* that topic in the prompt's index, and a store matching content alone
|
|
892
|
+
* would then miss the very query the listing invites — an alias in `about`
|
|
893
|
+
* against a pronoun in `content` is the case the field exists to serve.
|
|
894
|
+
* Written once here so the FTS `tokens` column and the in-memory filter
|
|
895
|
+
* cannot disagree about what a store holds.
|
|
896
|
+
*/
|
|
897
|
+
export declare const memoryEntryTokens: (entry: Pick<MemoryEntry, "content" | "about">) => string[];
|
|
898
|
+
/**
|
|
899
|
+
* Whether an entry matches a tokenized query: every query token must be
|
|
900
|
+
* present as a whole token of its content or of one of its `about` keys. A
|
|
901
|
+
* query that tokenized to nothing matches nothing — recall with no
|
|
902
|
+
* searchable terms is a question that was not asked, not a request for
|
|
903
|
+
* everything.
|
|
904
|
+
*/
|
|
905
|
+
export declare const memoryQueryMatches: (entry: Pick<MemoryEntry, "content" | "about">, queryTokens: readonly string[]) => boolean;
|
|
906
|
+
/**
|
|
907
|
+
* Where an entry sits relative to its validity window at a given instant.
|
|
908
|
+
* `current` is the only status that is true *now*; both others leave the
|
|
909
|
+
* injected slice and stay findable by `search`, marked.
|
|
763
910
|
*/
|
|
764
|
-
export
|
|
911
|
+
export type MemoryValidity = 'current' | 'not-yet-valid' | 'expired';
|
|
912
|
+
/**
|
|
913
|
+
* One rule, both bounds: a fact holds from `validFrom` (inclusive) until
|
|
914
|
+
* `validUntil` (exclusive). Pure in the entry and the clock, so both store
|
|
915
|
+
* implementations compute the same answer from the record alone.
|
|
916
|
+
*/
|
|
917
|
+
export declare const memoryValidityAt: (entry: Pick<MemoryEntry, "validFrom" | "validUntil">, at: Date) => MemoryValidity;
|
|
918
|
+
/** Shorthand for the only status that is true now. */
|
|
919
|
+
export declare const isMemoryEntryCurrent: (entry: Pick<MemoryEntry, "validFrom" | "validUntil">, at: Date) => boolean;
|
|
920
|
+
/**
|
|
921
|
+
* The ids retired by supersession at a given instant, over one agent's
|
|
922
|
+
* un-tombstoned entries.
|
|
923
|
+
*
|
|
924
|
+
* An entry is retired exactly while a successor of it is *current*, which
|
|
925
|
+
* is the only reading that keeps supersession and validity from cancelling
|
|
926
|
+
* each other: a successor dated from next Monday retiring its predecessor
|
|
927
|
+
* today would leave neither fact available as true now — the old one
|
|
928
|
+
* retired, the new one excluded as not-yet-valid — so the agent forgets
|
|
929
|
+
* something it still believes and gains nothing for it.
|
|
930
|
+
*
|
|
931
|
+
* Two successors of the same entry both retire it, in either order, and
|
|
932
|
+
* both stay live. The invariant is the *retirement*, not a unique
|
|
933
|
+
* replacement: each successor is a fact the agent wrote, and retiring one
|
|
934
|
+
* to make the other unique would delete a write nobody asked to retract.
|
|
935
|
+
* Two live successors that disagree are a contradiction in content, which
|
|
936
|
+
* is the maintenance pass's job and not a storage race.
|
|
937
|
+
*
|
|
938
|
+
* Callers pass the agent's un-tombstoned entries, so forgetting a successor
|
|
939
|
+
* releases the entry it retired — a revision the agent took back leaves the
|
|
940
|
+
* fact it replaced standing, which is the recoverable direction in a store
|
|
941
|
+
* whose whole posture is that nothing is lost.
|
|
942
|
+
*/
|
|
943
|
+
export declare const supersededMemoryIdsAt: (entries: readonly MemoryEntry[], at: Date) => Set<string>;
|
|
765
944
|
/**
|
|
766
945
|
* Recall order — newest first, ties on `createdAt` broken by entry id
|
|
767
946
|
* ascending. The tie-break is not about frequency: without one, two
|
|
@@ -772,10 +951,30 @@ export declare const memoryQueryMatches: (content: string, queryTokens: readonly
|
|
|
772
951
|
export declare const compareMemoryRecallOrder: (a: MemoryEntry, b: MemoryEntry) => number;
|
|
773
952
|
/** Chronological order (oldest first), same tie-break — how a bounded `list` presents its slice. */
|
|
774
953
|
export declare const compareMemoryChronology: (a: MemoryEntry, b: MemoryEntry) => number;
|
|
954
|
+
/**
|
|
955
|
+
* How a read orders what it found. Named in the request and reported in the
|
|
956
|
+
* result, rather than frozen in the contract.
|
|
957
|
+
*
|
|
958
|
+
* `recency` is mandatory, is the default, and is what the parity tests
|
|
959
|
+
* assert. `relevance` and `hybrid` are optional: a store that lacks the one
|
|
960
|
+
* asked for **serves `recency` and says so** rather than erroring, so two
|
|
961
|
+
* implementations stay honest about ordering without memory being frozen at
|
|
962
|
+
* clock order forever — and an embeddings-backed store behind a plugin seam
|
|
963
|
+
* becomes an additive capability rather than a contract break.
|
|
964
|
+
*/
|
|
965
|
+
export type MemoryRankingStrategy = 'recency' | 'relevance' | 'hybrid';
|
|
966
|
+
export declare const MEMORY_RANKING_STRATEGIES: readonly ["recency", "relevance", "hybrid"];
|
|
967
|
+
export declare const isMemoryRankingStrategy: (value: unknown) => value is MemoryRankingStrategy;
|
|
775
968
|
export interface MemoryReadResult {
|
|
776
969
|
entries: MemoryEntry[];
|
|
777
970
|
/** True when live entries beyond these existed — the entry limit or the byte budget bound, whichever bit first. */
|
|
778
971
|
truncated: boolean;
|
|
972
|
+
/**
|
|
973
|
+
* The ordering the store actually applied. Present on a `search`, where
|
|
974
|
+
* the caller may have asked for one the store does not implement; absent
|
|
975
|
+
* on `list`, which is chronological by definition.
|
|
976
|
+
*/
|
|
977
|
+
strategy?: MemoryRankingStrategy;
|
|
779
978
|
}
|
|
780
979
|
export interface MemoryListOptions {
|
|
781
980
|
/**
|
|
@@ -786,6 +985,53 @@ export interface MemoryListOptions {
|
|
|
786
985
|
* feed a model always pass a bound.
|
|
787
986
|
*/
|
|
788
987
|
limit?: number;
|
|
988
|
+
/**
|
|
989
|
+
* Which entries count as live for this read. `current` — the default,
|
|
990
|
+
* and what "what is true now" means — drops entries outside their
|
|
991
|
+
* validity window; `all` keeps them, for the operator views that exist
|
|
992
|
+
* to show an expired fact *as* expired. Superseded and forgotten entries
|
|
993
|
+
* are out of both: those are retirements, not calendar questions.
|
|
994
|
+
*/
|
|
995
|
+
validity?: 'current' | 'all';
|
|
996
|
+
}
|
|
997
|
+
/** Which pins a read wants — see `AgentMemoryStore.pinned`. */
|
|
998
|
+
export interface MemoryPinnedOptions {
|
|
999
|
+
/**
|
|
1000
|
+
* `current`, the default, is the pinned core as the prompt carries it:
|
|
1001
|
+
* live and true now. `allocated` is everything **holding budget** —
|
|
1002
|
+
* superseded and out-of-window pins included — which is a different
|
|
1003
|
+
* question and the one an operator, or a caller re-allocating a merged
|
|
1004
|
+
* budget, actually has. A pin nothing admits exists is one nobody can
|
|
1005
|
+
* unpin, and it is exactly what makes a later pin refuse.
|
|
1006
|
+
*/
|
|
1007
|
+
include?: 'current' | 'allocated';
|
|
1008
|
+
}
|
|
1009
|
+
export interface MemorySearchOptions {
|
|
1010
|
+
/** Clamped by `clampMemoryRecallLimit`; the store bounds it either way. */
|
|
1011
|
+
limit?: number;
|
|
1012
|
+
/** The ordering asked for. See `MemoryRankingStrategy` — an unsupported one is served as `recency`. */
|
|
1013
|
+
strategy?: MemoryRankingStrategy;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* What a writer says about a new entry beyond its text. Every field is
|
|
1017
|
+
* optional, and an `append` that passes none writes exactly the four-field
|
|
1018
|
+
* line an older build would have written.
|
|
1019
|
+
*/
|
|
1020
|
+
export interface MemoryAppendOptions {
|
|
1021
|
+
metadata?: JsonObject;
|
|
1022
|
+
provenance?: MemoryProvenance;
|
|
1023
|
+
kind?: MemoryEntryKind;
|
|
1024
|
+
about?: string[];
|
|
1025
|
+
validFrom?: string;
|
|
1026
|
+
validUntil?: string;
|
|
1027
|
+
/**
|
|
1028
|
+
* The entry this one replaces. The store resolves it against the
|
|
1029
|
+
* caller's **own** live entries before anything is appended and throws
|
|
1030
|
+
* otherwise: the record lane is shared across agents, and a writer that
|
|
1031
|
+
* skipped the check would let one agent retire another's memory by
|
|
1032
|
+
* naming its id.
|
|
1033
|
+
*/
|
|
1034
|
+
supersedes?: string;
|
|
789
1035
|
}
|
|
790
1036
|
/** Clamp a caller-chosen search limit into the store's own bounds. */
|
|
791
1037
|
export declare const clampMemoryRecallLimit: (limit?: number) => number;
|
|
@@ -803,7 +1049,130 @@ export declare const boundMemoryRead: (candidates: readonly MemoryEntry[], limit
|
|
|
803
1049
|
* presentation order are different things; conflating them is the bug this
|
|
804
1050
|
* helper exists so nobody writes three times.
|
|
805
1051
|
*/
|
|
806
|
-
export declare const boundMemoryList: (candidates: readonly MemoryEntry[], limit: number) => MemoryReadResult;
|
|
1052
|
+
export declare const boundMemoryList: (candidates: readonly MemoryEntry[], limit: number, maxBytes?: number) => MemoryReadResult;
|
|
1053
|
+
/**
|
|
1054
|
+
* What the pin write path decides, and the sentence it says when it
|
|
1055
|
+
* refuses. Split out because the refusal has to name the cap — an operator
|
|
1056
|
+
* who cannot see why a pin bounced will assume it worked.
|
|
1057
|
+
*/
|
|
1058
|
+
export interface MemoryPinOutcome {
|
|
1059
|
+
pinned: boolean;
|
|
1060
|
+
/** Present when `pinned` is false: what happened, as a full sentence. */
|
|
1061
|
+
reason?: string;
|
|
1062
|
+
/** Content bytes the effective pinned set holds after this call. */
|
|
1063
|
+
bytes: number;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* The pinned set the record produces, and the rule both stores replay it
|
|
1067
|
+
* by: **pins take effect in the order their records appear in the file,
|
|
1068
|
+
* until the cap, and the remainder are recorded but inert.**
|
|
1069
|
+
*
|
|
1070
|
+
* The write path refusing an over-cap pin is the common case and stays the
|
|
1071
|
+
* rule. This is for the race it cannot see: the CLI and the daemon build
|
|
1072
|
+
* separate stores over the same file and coordinate by `O_APPEND` alone, so
|
|
1073
|
+
* two processes pinning near the limit can both read the same total, both
|
|
1074
|
+
* accept, and both append. Replay gives them one effective set instead of
|
|
1075
|
+
* an injected slice that either overruns its budget or drops a pin by
|
|
1076
|
+
* whichever order it happened to read.
|
|
1077
|
+
*
|
|
1078
|
+
* **Append order, not `(createdAt, id)`.** A timestamp is the wrong key for
|
|
1079
|
+
* a budget: a pin written later with a skewed-earlier clock would sort
|
|
1080
|
+
* ahead of a pin that is already effective and consume the budget out from
|
|
1081
|
+
* under it, making an accepted pin inert after the fact and contradicting
|
|
1082
|
+
* the refusal the cap promises. `O_APPEND` already supplies a total order
|
|
1083
|
+
* no later writer can insert itself into. Recall ordering keeps its
|
|
1084
|
+
* `(createdAt, id)` rule — presenting facts newest-first is about meaning,
|
|
1085
|
+
* allocating a scarce budget is about arrival.
|
|
1086
|
+
*
|
|
1087
|
+
* Validity is deliberately *not* consulted here. The budget is a property
|
|
1088
|
+
* of the record, so it must not change when a clock ticks past a
|
|
1089
|
+
* `validUntil`; an out-of-window pin holds its place and is dropped from
|
|
1090
|
+
* the rendered core by `selectMemoryInjection`, which is where "true now"
|
|
1091
|
+
* is decided.
|
|
1092
|
+
*/
|
|
1093
|
+
export declare const applyMemoryPinBudget: (pinnedIds: readonly string[], contentBytesFor: (id: string) => number | undefined, maxBytes?: number) => {
|
|
1094
|
+
effective: string[];
|
|
1095
|
+
inert: string[];
|
|
1096
|
+
bytes: number;
|
|
1097
|
+
};
|
|
1098
|
+
/** One line of the prompt's topic index: what the agent knows about, and how much. */
|
|
1099
|
+
export interface MemoryTopic {
|
|
1100
|
+
/** The `about` key, in the spelling the first entry to use it wrote. */
|
|
1101
|
+
name: string;
|
|
1102
|
+
count: number;
|
|
1103
|
+
/** The newest `createdAt` among the entries filed under it. */
|
|
1104
|
+
lastUpdatedAt: string;
|
|
1105
|
+
/**
|
|
1106
|
+
* The least trusted label among those entries. The topic index is a view
|
|
1107
|
+
* derived from entries, so it renders under the labelling invariant like
|
|
1108
|
+
* every other one: an entity name an `external` entry supplied must not
|
|
1109
|
+
* appear under the heading for the agent's own conclusions.
|
|
1110
|
+
*/
|
|
1111
|
+
trust: TrustLevel;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* The topic index, built from `about` keys over the entries a store holds:
|
|
1115
|
+
* not facts, but what the agent *knows about*. Counts and last-updated,
|
|
1116
|
+
* ordered by count descending then name, so the block is stable across
|
|
1117
|
+
* turns and the biggest topics survive the budget.
|
|
1118
|
+
*
|
|
1119
|
+
* Shared rather than per-store for the usual reason: two implementations
|
|
1120
|
+
* building this differently would advertise different stores over the same
|
|
1121
|
+
* record.
|
|
1122
|
+
*/
|
|
1123
|
+
export declare const collectMemoryTopics: (entries: readonly MemoryEntry[]) => MemoryTopic[];
|
|
1124
|
+
/**
|
|
1125
|
+
* The three bounded blocks a turn injects, in place of the one recency
|
|
1126
|
+
* slice memory used to send.
|
|
1127
|
+
*
|
|
1128
|
+
* They are three blocks *within* one `memory` section, never three sections
|
|
1129
|
+
* of their own — an adapter that places the volatile section separately
|
|
1130
|
+
* finds it by kind, so siblings sharing the kind would move one and drop
|
|
1131
|
+
* the rest of the request on the floor.
|
|
1132
|
+
*/
|
|
1133
|
+
export interface MemoryInjection {
|
|
1134
|
+
/**
|
|
1135
|
+
* Always present when anything is pinned, hard-capped at
|
|
1136
|
+
* `MEMORY_PINNED_MAX_BYTES`. This is what makes an agent still know the
|
|
1137
|
+
* basics after a year with no search at all.
|
|
1138
|
+
*/
|
|
1139
|
+
pinned: MemoryEntry[];
|
|
1140
|
+
/** What the agent knows about — the block that turns `memory.recall` from a guess into a targeted read. */
|
|
1141
|
+
topics: MemoryTopic[];
|
|
1142
|
+
/** The recency tail, so a cold store with nothing pinned behaves as it did before. */
|
|
1143
|
+
recent: MemoryEntry[];
|
|
1144
|
+
}
|
|
1145
|
+
/** Empty in all three blocks — what a host with no memory store injects. */
|
|
1146
|
+
export declare const emptyMemoryInjection: () => MemoryInjection;
|
|
1147
|
+
/**
|
|
1148
|
+
* Bound the three blocks against their budgets and keep them disjoint: a
|
|
1149
|
+
* pinned fact already in context is not worth a second copy in the tail,
|
|
1150
|
+
* and the tail is the block that shrinks, never the pinned core. The
|
|
1151
|
+
* pinned set is never pushed out by volume — that is the whole reason it
|
|
1152
|
+
* is a separate budget rather than a ranking bonus.
|
|
1153
|
+
*/
|
|
1154
|
+
export declare const selectMemoryInjection: (input: {
|
|
1155
|
+
pinned: readonly MemoryEntry[];
|
|
1156
|
+
topics: readonly MemoryTopic[];
|
|
1157
|
+
recent: readonly MemoryEntry[];
|
|
1158
|
+
}) => MemoryInjection;
|
|
1159
|
+
/**
|
|
1160
|
+
* Read the three blocks out of a store. The policy — what counts as
|
|
1161
|
+
* current, what the budgets are, which block a fact lands in — lives in
|
|
1162
|
+
* `selectMemoryInjection` and the store's own `list`, so a store that
|
|
1163
|
+
* implements none of the optional methods still injects a recency tail and
|
|
1164
|
+
* nothing is special-cased at the call site.
|
|
1165
|
+
*/
|
|
1166
|
+
export declare const buildMemoryInjection: (store: AgentMemoryStore, agentId: string) => Promise<MemoryInjection>;
|
|
1167
|
+
/**
|
|
1168
|
+
* The three blocks as a request may carry them: a bare array is the recency
|
|
1169
|
+
* tail alone, which is what `memory` meant before pinning and the topic
|
|
1170
|
+
* index existed and is still what a host building a request by hand may
|
|
1171
|
+
* pass.
|
|
1172
|
+
*/
|
|
1173
|
+
export declare const asMemoryInjection: (memory: readonly MemoryEntry[] | MemoryInjection | undefined) => MemoryInjection;
|
|
1174
|
+
/** Every entry a turn injects, in the order they reach the prompt — what taints the session. */
|
|
1175
|
+
export declare const memoryInjectionEntries: (injection: readonly MemoryEntry[] | MemoryInjection | undefined) => MemoryEntry[];
|
|
807
1176
|
/**
|
|
808
1177
|
* Durable, searchable memory an agent writes and reads deliberately. The
|
|
809
1178
|
* per-agent key is the access boundary — every method takes the agent id the
|
|
@@ -823,19 +1192,30 @@ export declare const boundMemoryList: (candidates: readonly MemoryEntry[], limit
|
|
|
823
1192
|
* `MEMORY_READ_MAX_BYTES` when a limit is in play; `forget` tombstones
|
|
824
1193
|
* rather than deletes, and `audit` is where tombstoned entries remain
|
|
825
1194
|
* visible to an operator.
|
|
1195
|
+
* - An entry is **not live** when a tombstone retires it or a *current*
|
|
1196
|
+
* successor supersedes it (`supersededMemoryIdsAt`). An entry outside its
|
|
1197
|
+
* validity window is a different thing: still live, out of `list` by
|
|
1198
|
+
* default because it is not true now, and still found by `search` — where
|
|
1199
|
+
* the caller reads its status with `memoryValidityAt`.
|
|
826
1200
|
*/
|
|
827
1201
|
export interface AgentMemoryStore {
|
|
828
1202
|
/**
|
|
829
1203
|
* Refuses content over `MEMORY_ENTRY_MAX_BYTES` rather than truncating it.
|
|
830
|
-
* `provenance` is what the writer knows about where the fact came
|
|
831
|
-
* an append without one stores an entry that reads as `unknown`,
|
|
832
|
-
* is the honest label for a writer that did not say.
|
|
1204
|
+
* `options.provenance` is what the writer knows about where the fact came
|
|
1205
|
+
* from; an append without one stores an entry that reads as `unknown`,
|
|
1206
|
+
* which is the honest label for a writer that did not say. Throws when
|
|
1207
|
+
* `options.supersedes` names an id that is not a live entry of this agent.
|
|
833
1208
|
*/
|
|
834
|
-
append(agentId: string, content: string,
|
|
835
|
-
/** Live entries, oldest first. See `MemoryListOptions` for the bounded
|
|
1209
|
+
append(agentId: string, content: string, options?: MemoryAppendOptions): Promise<MemoryEntry>;
|
|
1210
|
+
/** Live entries, oldest first. See `MemoryListOptions` for the bounded and the out-of-window forms. */
|
|
836
1211
|
list(agentId: string, options?: MemoryListOptions): Promise<MemoryReadResult>;
|
|
837
|
-
/**
|
|
838
|
-
|
|
1212
|
+
/**
|
|
1213
|
+
* Live entries matching the literal query, newest first, bounded —
|
|
1214
|
+
* entries outside their validity window included, because keeping an
|
|
1215
|
+
* expired fact findable is the point of separating validity from
|
|
1216
|
+
* supersession. Matching covers content and `about` keys alike.
|
|
1217
|
+
*/
|
|
1218
|
+
search(agentId: string, query: string, options?: MemorySearchOptions): Promise<MemoryReadResult>;
|
|
839
1219
|
/** Tombstones a live entry. False when no live entry of this agent has that id. */
|
|
840
1220
|
forget(agentId: string, entryId: string): Promise<boolean>;
|
|
841
1221
|
/** The operator's audit read: every entry, tombstoned included, oldest first. */
|
|
@@ -852,21 +1232,160 @@ export interface AgentMemoryStore {
|
|
|
852
1232
|
* operator has no way out of `unknown` but rewriting the record.
|
|
853
1233
|
*/
|
|
854
1234
|
reassertTrust?(agentId: string, entryId: string, trust: TrustLevel): Promise<boolean>;
|
|
1235
|
+
/**
|
|
1236
|
+
* Pin a live entry into the core the prompt always carries. Refuses over
|
|
1237
|
+
* `MEMORY_PINNED_MAX_BYTES` naming the cap, and **never evicts**: a
|
|
1238
|
+
* pinned set that silently drops its oldest member is a pin that does not
|
|
1239
|
+
* mean anything. Like `supersedes`, it resolves the id against the
|
|
1240
|
+
* caller's own live entries before recording anything.
|
|
1241
|
+
*
|
|
1242
|
+
* Optional: a host whose store omits it gets no pinned core — injection
|
|
1243
|
+
* falls back to the topic index and the recency tail, which is what
|
|
1244
|
+
* memory did before pinning existed.
|
|
1245
|
+
*/
|
|
1246
|
+
pin?(agentId: string, entryId: string): Promise<MemoryPinOutcome>;
|
|
1247
|
+
/** Undo a pin. False when this agent has no effective pin on that id. */
|
|
1248
|
+
unpin?(agentId: string, entryId: string): Promise<boolean>;
|
|
1249
|
+
/**
|
|
1250
|
+
* The effective pinned set, oldest first — see `applyMemoryPinBudget` for
|
|
1251
|
+
* what "effective" means, and `MemoryPinnedOptions` for the out-of-window
|
|
1252
|
+
* ones that hold budget without reaching the prompt.
|
|
1253
|
+
*/
|
|
1254
|
+
pinned?(agentId: string, options?: MemoryPinnedOptions): Promise<MemoryEntry[]>;
|
|
1255
|
+
/**
|
|
1256
|
+
* What the agent knows *about*, from the `about` keys of everything true
|
|
1257
|
+
* now. Built with `collectMemoryTopics` so no two stores can advertise
|
|
1258
|
+
* the same record differently.
|
|
1259
|
+
*
|
|
1260
|
+
* Optional: a host whose store omits it gets no topic index, and its
|
|
1261
|
+
* agent is back to guessing query terms.
|
|
1262
|
+
*/
|
|
1263
|
+
topics?(agentId: string): Promise<MemoryTopic[]>;
|
|
1264
|
+
/**
|
|
1265
|
+
* Append entries verbatim — ids, timestamps, kinds, validity and all —
|
|
1266
|
+
* under this agent's key. The operator's import path and the eval
|
|
1267
|
+
* harness's corpus loader; no tool reaches it, because an agent able to
|
|
1268
|
+
* choose its own entry ids could name one another record already refers
|
|
1269
|
+
* to.
|
|
1270
|
+
*
|
|
1271
|
+
* Entries whose id the agent already holds are skipped rather than
|
|
1272
|
+
* duplicated, so a re-run of an import is a no-op rather than a second
|
|
1273
|
+
* copy of everything.
|
|
1274
|
+
*/
|
|
1275
|
+
importEntries?(agentId: string, entries: readonly MemoryEntry[]): Promise<MemoryImportResult>;
|
|
1276
|
+
}
|
|
1277
|
+
/** What an import did: what landed, and what was already there. */
|
|
1278
|
+
export interface MemoryImportResult {
|
|
1279
|
+
imported: number;
|
|
1280
|
+
/** Ids skipped because this agent already holds an entry with that id. */
|
|
1281
|
+
skipped: string[];
|
|
855
1282
|
}
|
|
1283
|
+
/**
|
|
1284
|
+
* The refusal an over-cap pin says, naming the cap. An operator who cannot
|
|
1285
|
+
* see why a pin bounced will assume it worked, which is the failure the
|
|
1286
|
+
* refuse-never-evict rule exists to avoid in the first place.
|
|
1287
|
+
*/
|
|
1288
|
+
export declare const pinnedCapRefusal: (heldBytes: number, wantedBytes: number) => string;
|
|
1289
|
+
/**
|
|
1290
|
+
* The other refusal, and the one a byte total cannot express: an inert pin
|
|
1291
|
+
* already stands in the record.
|
|
1292
|
+
*
|
|
1293
|
+
* `applyMemoryPinBudget` is a **prefix** rule, so once one pin overruns the
|
|
1294
|
+
* cap nothing recorded after it takes effect, whatever its size. A write
|
|
1295
|
+
* path that tested only the effective byte total would therefore accept a
|
|
1296
|
+
* small pin, append it, and report success for something `pinned` omits on
|
|
1297
|
+
* the very next read — an eviction wearing the refusal's clothes, which is
|
|
1298
|
+
* the one thing the cap promises cannot happen. The blocking id is named
|
|
1299
|
+
* because it is the only thing the operator can act on: the effective set
|
|
1300
|
+
* is under the cap and unpinning from *it* frees nothing.
|
|
1301
|
+
*/
|
|
1302
|
+
export declare const pinnedInertRefusal: (blockingId: string) => string;
|
|
1303
|
+
/**
|
|
1304
|
+
* The write-path half of the per-agent boundary, shared by every store: an
|
|
1305
|
+
* id a new record names must resolve to a live entry the caller owns
|
|
1306
|
+
* *before* anything is appended.
|
|
1307
|
+
*
|
|
1308
|
+
* This is a security requirement rather than input hygiene. A store's
|
|
1309
|
+
* forgotten set is computed from the record without an agent filter, so a
|
|
1310
|
+
* writer that skipped this would let agent A retire agent B's memory by
|
|
1311
|
+
* naming its id — gone from B's `list`, its `search`, and its prompt, with
|
|
1312
|
+
* B's own `forget` never called.
|
|
1313
|
+
*/
|
|
1314
|
+
export declare const assertSupersedableMemoryEntry: (live: readonly MemoryEntry[], entryId: string) => void;
|
|
1315
|
+
/**
|
|
1316
|
+
* An entry's optional fields as they are safe to read back from a record
|
|
1317
|
+
* nobody validated — a hand-edited line, an imported file, a newer build's
|
|
1318
|
+
* output. A field of the wrong shape is dropped rather than thrown over:
|
|
1319
|
+
* the record is the operator's to edit, and a bad `about` is not worth
|
|
1320
|
+
* losing a fact for. Written once so the file store's parser and the import
|
|
1321
|
+
* path cannot disagree about what a line means.
|
|
1322
|
+
*/
|
|
1323
|
+
export declare const memoryEntryFields: (value: Pick<MemoryEntry, "kind" | "about" | "validFrom" | "validUntil" | "supersedes">) => Pick<MemoryEntry, "kind" | "about" | "validFrom" | "validUntil" | "supersedes">;
|
|
1324
|
+
/**
|
|
1325
|
+
* One entry as an import should store it: re-keyed to the importing agent,
|
|
1326
|
+
* optional fields validated, and `usage` dropped — usage is an observation
|
|
1327
|
+
* about reading this install did, never something a file carries in.
|
|
1328
|
+
*/
|
|
1329
|
+
export declare const importableMemoryEntry: (entry: MemoryEntry, agentId: string) => MemoryEntry;
|
|
856
1330
|
export declare class InMemoryAgentMemoryStore implements AgentMemoryStore {
|
|
857
1331
|
private entries;
|
|
1332
|
+
/**
|
|
1333
|
+
* The pin lane, per agent, in append order — the same total order the
|
|
1334
|
+
* file store gets from `O_APPEND`, and for the same reason: a budget is
|
|
1335
|
+
* allocated by arrival, never by timestamp. A pin already held is not
|
|
1336
|
+
* re-appended, so re-pinning cannot move an entry to the back.
|
|
1337
|
+
*/
|
|
1338
|
+
private pins;
|
|
1339
|
+
/**
|
|
1340
|
+
* Keyed by (agent, entry), never by entry alone: `importEntries`
|
|
1341
|
+
* preserves ids while re-keying entries, so two agents legitimately hold
|
|
1342
|
+
* the same id and one agent's reads must not move the other's counters.
|
|
1343
|
+
*/
|
|
1344
|
+
private usage;
|
|
1345
|
+
/**
|
|
1346
|
+
* Every id this store holds, minted or imported. `append` skips past the
|
|
1347
|
+
* ones already taken, because an import that preserved `a:memory:1` and
|
|
1348
|
+
* left the counter at zero would mint that id again — and two entries
|
|
1349
|
+
* sharing an id are indistinguishable to tombstones, supersession, pins,
|
|
1350
|
+
* and usage alike.
|
|
1351
|
+
*/
|
|
1352
|
+
private takenIds;
|
|
858
1353
|
private counter;
|
|
859
1354
|
private readonly now;
|
|
860
1355
|
constructor(options?: {
|
|
861
1356
|
now?: () => Date;
|
|
862
1357
|
});
|
|
863
|
-
append(agentId: string, content: string,
|
|
1358
|
+
append(agentId: string, content: string, options?: MemoryAppendOptions): Promise<MemoryEntry>;
|
|
1359
|
+
/** Not tombstoned and not superseded by a successor that is current at `at`. */
|
|
864
1360
|
private live;
|
|
865
1361
|
list(agentId: string, options?: MemoryListOptions): Promise<MemoryReadResult>;
|
|
866
|
-
search(agentId: string, query: string,
|
|
1362
|
+
search(agentId: string, query: string, options?: MemorySearchOptions): Promise<MemoryReadResult>;
|
|
1363
|
+
private usageKey;
|
|
1364
|
+
private noteRecalled;
|
|
1365
|
+
/**
|
|
1366
|
+
* The entry a mutation may touch: live, which means un-tombstoned *and*
|
|
1367
|
+
* not superseded. Resolving against the raw array instead would let an
|
|
1368
|
+
* agent forget a predecessor a current successor retired — and forgetting
|
|
1369
|
+
* that successor is documented to release it, which a tombstone on the
|
|
1370
|
+
* predecessor would then silently prevent.
|
|
1371
|
+
*/
|
|
1372
|
+
private mutable;
|
|
867
1373
|
forget(agentId: string, entryId: string): Promise<boolean>;
|
|
868
1374
|
audit(agentId: string): Promise<MemoryAuditEntry[]>;
|
|
869
1375
|
reassertTrust(agentId: string, entryId: string, trust: TrustLevel): Promise<boolean>;
|
|
1376
|
+
/**
|
|
1377
|
+
* Allocated over un-tombstoned entries, never the live view: the budget
|
|
1378
|
+
* is a property of the record and must not move when a successor's
|
|
1379
|
+
* window opens or a clock ticks. Sizing it from the live set would free a
|
|
1380
|
+
* superseded pin's bytes, admit a later pin into the space, and make that
|
|
1381
|
+
* later pin inert the moment the successor was forgotten.
|
|
1382
|
+
*/
|
|
1383
|
+
private pinBudget;
|
|
1384
|
+
pin(agentId: string, entryId: string): Promise<MemoryPinOutcome>;
|
|
1385
|
+
unpin(agentId: string, entryId: string): Promise<boolean>;
|
|
1386
|
+
pinned(agentId: string, options?: MemoryPinnedOptions): Promise<MemoryEntry[]>;
|
|
1387
|
+
topics(agentId: string): Promise<MemoryTopic[]>;
|
|
1388
|
+
importEntries(agentId: string, entries: readonly MemoryEntry[]): Promise<MemoryImportResult>;
|
|
870
1389
|
}
|
|
871
1390
|
/**
|
|
872
1391
|
* Resolves named credentials for an agent. Implementations must enforce the
|
|
@@ -981,10 +1500,51 @@ export declare const uncachedInputTokens: (total: number, ...cached: Array<numbe
|
|
|
981
1500
|
*/
|
|
982
1501
|
export declare const totalTokenUsage: (records: readonly TokenUsage[]) => TokenUsage | undefined;
|
|
983
1502
|
export interface ProviderRequest {
|
|
1503
|
+
/**
|
|
1504
|
+
* The live session. **Persistable**: a provider that saves it, or hands
|
|
1505
|
+
* it to `executeHostedToolCall`, is writing the real conversation — so
|
|
1506
|
+
* this is always the whole session, never a view. Read the transcript
|
|
1507
|
+
* through `transcriptOf(request)` instead of from here.
|
|
1508
|
+
*/
|
|
984
1509
|
session: Session;
|
|
1510
|
+
/**
|
|
1511
|
+
* The transcript to send, when it is not the whole of `session.messages`
|
|
1512
|
+
* — the tail left by the session's context floor, with a note in place
|
|
1513
|
+
* of what was cut.
|
|
1514
|
+
*
|
|
1515
|
+
* Separate from `session` because the two are used for different things
|
|
1516
|
+
* and only one of them may be written back. The harness providers pass
|
|
1517
|
+
* `request.session` to `executeHostedToolCall`, which appends tool
|
|
1518
|
+
* records and saves; the fallback wrapper persists `request.session` to
|
|
1519
|
+
* make its switch durable. Handing either a shortened *session* would
|
|
1520
|
+
* write the window over the stored transcript and lose everything below
|
|
1521
|
+
* the floor, permanently.
|
|
1522
|
+
*
|
|
1523
|
+
* Absent means "all of them", so an adapter that ignores this field
|
|
1524
|
+
* behaves exactly as it did — which is right for a harness provider,
|
|
1525
|
+
* since it manages its own context and the window is not its to apply.
|
|
1526
|
+
*/
|
|
1527
|
+
messages?: readonly Message[];
|
|
985
1528
|
tools?: ToolDescriptor[];
|
|
986
|
-
/**
|
|
987
|
-
|
|
1529
|
+
/**
|
|
1530
|
+
* `none` asks for an answer in words: the tools stay declared, because
|
|
1531
|
+
* the history holds calls to them and an API that is sent tool calls
|
|
1532
|
+
* with no tools to match refuses the request, but the model may not
|
|
1533
|
+
* call one. Sent on the turn that wraps up a message which has used its
|
|
1534
|
+
* `maxTurns` (see `TURN_LIMIT_NOTE`).
|
|
1535
|
+
*
|
|
1536
|
+
* Optional to honour. A provider that ignores it sends a request the
|
|
1537
|
+
* model may still answer with a call, and the runner then fails the turn
|
|
1538
|
+
* the way it did before wrap-up existed, rather than run the call.
|
|
1539
|
+
*/
|
|
1540
|
+
toolChoice?: 'none';
|
|
1541
|
+
/**
|
|
1542
|
+
* Agent-scoped long-term memory as the turn injects it: the pinned core,
|
|
1543
|
+
* the topic index, and the recency tail. A bare array is the tail alone —
|
|
1544
|
+
* what `memory` meant before the three blocks existed, and still what a
|
|
1545
|
+
* host building a request by hand may pass.
|
|
1546
|
+
*/
|
|
1547
|
+
memory?: MemoryEntry[] | MemoryInjection;
|
|
988
1548
|
/**
|
|
989
1549
|
* The skills enabled for this agent — descriptors only, one prompt line
|
|
990
1550
|
* each. Bodies never travel here; they arrive through `skill.read` when
|
|
@@ -1039,6 +1599,16 @@ export interface ProviderRequest {
|
|
|
1039
1599
|
*/
|
|
1040
1600
|
signal?: AbortSignal;
|
|
1041
1601
|
}
|
|
1602
|
+
/**
|
|
1603
|
+
* The transcript a provider should send: the request's window when it has
|
|
1604
|
+
* one, the whole session otherwise.
|
|
1605
|
+
*
|
|
1606
|
+
* One implementation because there is one rule, and because the wrong
|
|
1607
|
+
* answer is silent — an adapter reading `session.messages` directly still
|
|
1608
|
+
* works, it just ignores the floor and sends the request that was already
|
|
1609
|
+
* refused.
|
|
1610
|
+
*/
|
|
1611
|
+
export declare const transcriptOf: (request: Pick<ProviderRequest, "session" | "messages">) => readonly Message[];
|
|
1042
1612
|
export interface ProviderResponse {
|
|
1043
1613
|
parts: ProviderPart[];
|
|
1044
1614
|
/**
|
|
@@ -1334,6 +1904,22 @@ export interface PromptTextOptions {
|
|
|
1334
1904
|
* followed more often than one read an hour ago.
|
|
1335
1905
|
*/
|
|
1336
1906
|
export declare const UNADDRESSED_TURN_NOTE = "Nobody said that to you, and nobody is waiting on you. Reply only if you have something these people would want from you and do not have \u2014 an answer to a question left open, a correction to something wrong, a thing you were asked to watch for. Otherwise reply with nothing at all: no acknowledgement, no summary, no offer to help. Saying nothing is usually the right answer here.";
|
|
1907
|
+
/**
|
|
1908
|
+
* What the last turn of a message is told, once the message has used every
|
|
1909
|
+
* tool turn `maxTurns` allows it.
|
|
1910
|
+
*
|
|
1911
|
+
* The alternative is what this replaced: the turn failed with "exceeded the
|
|
1912
|
+
* maximum of 8 provider turns", every step it had taken sat in the
|
|
1913
|
+
* transcript unsummarized, and the person got an error for work that had
|
|
1914
|
+
* mostly succeeded. A limit on how far one message may run is a check-in
|
|
1915
|
+
* point, not a failure, so the agent says where it got to and the next
|
|
1916
|
+
* message carries on — the session keeps everything the turns did.
|
|
1917
|
+
*
|
|
1918
|
+
* Sent as the newest message of the request's window, never saved: it is
|
|
1919
|
+
* the runtime speaking for one call, and a transcript that kept it would
|
|
1920
|
+
* read to every later turn as something the person said.
|
|
1921
|
+
*/
|
|
1922
|
+
export declare const TURN_LIMIT_NOTE = "You have used every step this message allows, so you cannot call any more tools on it. Reply now, in words: say what you did, what you found, and what is left. If the work is unfinished, end by saying that replying \"continue\" picks it up from here.";
|
|
1337
1923
|
/** Reads the checkpoint off a session, if it is parked. */
|
|
1338
1924
|
export declare const readPendingApproval: (session: Session) => PendingApprovalRecord | undefined;
|
|
1339
1925
|
/**
|
|
@@ -1514,6 +2100,23 @@ export type StratusEvent = {
|
|
|
1514
2100
|
sessionId: string;
|
|
1515
2101
|
trust: TrustLevel;
|
|
1516
2102
|
source: string;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* The conversation outgrew the model's context window, so its oldest
|
|
2106
|
+
* messages are no longer sent. `droppedMessages` is what this trim gave
|
|
2107
|
+
* up; `floor` is the total now held back.
|
|
2108
|
+
*
|
|
2109
|
+
* Counts only — no content, for the same reason `session.tainted` names
|
|
2110
|
+
* only its source: this is what the daemon log records, and the log is a
|
|
2111
|
+
* trace, not a second transcript. Worth an event at all because it is
|
|
2112
|
+
* otherwise invisible: the answers stay plausible while the agent
|
|
2113
|
+
* quietly stops being able to remember the start of the conversation.
|
|
2114
|
+
*/
|
|
2115
|
+
| {
|
|
2116
|
+
type: 'session.context-trimmed';
|
|
2117
|
+
sessionId: string;
|
|
2118
|
+
droppedMessages: number;
|
|
2119
|
+
floor: number;
|
|
1517
2120
|
};
|
|
1518
2121
|
export type EventHandler = (event: StratusEvent) => void | Promise<void>;
|
|
1519
2122
|
export interface EventBusOptions {
|
|
@@ -1652,9 +2255,35 @@ export interface ChannelContribution {
|
|
|
1652
2255
|
* memories, the way a `tool-fs` that cached its roots would hand every
|
|
1653
2256
|
* agent the first agent's files.
|
|
1654
2257
|
*/
|
|
2258
|
+
/**
|
|
2259
|
+
* The revision of `AgentMemoryStore` a contributed store is built against.
|
|
2260
|
+
*
|
|
2261
|
+
* Bumped when the contract changes shape in a way a plugin compiled against
|
|
2262
|
+
* the old one cannot satisfy — which it *silently* cannot, because the
|
|
2263
|
+
* change that matters here is the shape of an argument rather than the
|
|
2264
|
+
* number of them. A v1 store's `search(agentId, query, limit)` still
|
|
2265
|
+
* accepts three arguments and still returns entries when the host hands it
|
|
2266
|
+
* an options object; it just binds `{ limit: 10 }` where a number belonged,
|
|
2267
|
+
* and answers with an unbounded read or none at all. No runtime check can
|
|
2268
|
+
* tell the two apart from the function, so the plugin declares it.
|
|
2269
|
+
*
|
|
2270
|
+
* - **1** — `append(agentId, content, metadata?, provenance?)` and
|
|
2271
|
+
* `search(agentId, query, limit?)`, the shape before 29.
|
|
2272
|
+
* - **2** — `append(agentId, content, options?)` and
|
|
2273
|
+
* `search(agentId, query, options?)`, plus the optional `pin`, `unpin`,
|
|
2274
|
+
* `pinned`, `topics`, and `importEntries`.
|
|
2275
|
+
*/
|
|
2276
|
+
export declare const MEMORY_STORE_CONTRACT_VERSION = 2;
|
|
1655
2277
|
export interface MemoryStoreContribution {
|
|
1656
2278
|
name: string;
|
|
1657
2279
|
store: AgentMemoryStore;
|
|
2280
|
+
/**
|
|
2281
|
+
* Must be `MEMORY_STORE_CONTRACT_VERSION`. A store declaring an older
|
|
2282
|
+
* revision — or none, which is what a plugin built before this field
|
|
2283
|
+
* existed produces — is refused at registration rather than routed
|
|
2284
|
+
* traffic it will mishandle without saying so.
|
|
2285
|
+
*/
|
|
2286
|
+
contract: typeof MEMORY_STORE_CONTRACT_VERSION;
|
|
1658
2287
|
}
|
|
1659
2288
|
/** An executor contributed by a plugin, selectable by name in a trusted config. */
|
|
1660
2289
|
export interface ExecutorContribution {
|
|
@@ -1709,8 +2338,12 @@ export declare class ChannelRegistry {
|
|
|
1709
2338
|
* memory tools are built before any plugin has loaded, so the store they
|
|
1710
2339
|
* hold has to be one that can change its mind — and an agent-by-agent
|
|
1711
2340
|
* answer is what keeps a contributed store from being process-global.
|
|
1712
|
-
*
|
|
1713
|
-
* that omitted
|
|
2341
|
+
* Every optional method is forwarded only when the chosen store has it: a
|
|
2342
|
+
* store that omitted one cannot do that thing, and this must not claim
|
|
2343
|
+
* otherwise. The routed store declares them all, because which store
|
|
2344
|
+
* serves an agent is not known until the call — so the honest answer for a
|
|
2345
|
+
* store that lacks one is the same answer the seam's own default gives
|
|
2346
|
+
* (nothing pinned, no topics, no re-assertion).
|
|
1714
2347
|
*/
|
|
1715
2348
|
export declare const createRoutedMemoryStore: (storeFor: (agentId: string) => AgentMemoryStore) => AgentMemoryStore;
|
|
1716
2349
|
/** The handle a plugin registers providers through. Manifest-bound; see `@stratusagent/plugins`. */
|
|
@@ -1737,6 +2370,57 @@ export interface ChannelRegistrationHandle {
|
|
|
1737
2370
|
*/
|
|
1738
2371
|
transportSecrets(kind: string): Promise<ChannelTransportSecrets>;
|
|
1739
2372
|
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Where each agent's files go, answered by the host.
|
|
2375
|
+
*
|
|
2376
|
+
* A seam rather than a path in a config block because the layout is this
|
|
2377
|
+
* repository's to own: the workspace is `~/.stratus/agents/<id>/workspace`
|
|
2378
|
+
* today and was `~/.stratus/workspaces/<id>` before it, and every plugin
|
|
2379
|
+
* that joined the agent id onto a root held a copy of that shape. Five of
|
|
2380
|
+
* them did, and all five had to change when it moved — which is the cost
|
|
2381
|
+
* the `workspaceRoot` key was introduced to avoid and did not.
|
|
2382
|
+
*
|
|
2383
|
+
* Asked per call, from `session.agent.id`, for the same reason credentials
|
|
2384
|
+
* are: the answer is the *calling* agent's, and one captured at setup is
|
|
2385
|
+
* the first agent's forever.
|
|
2386
|
+
*/
|
|
2387
|
+
export interface AgentWorkspaces {
|
|
2388
|
+
/**
|
|
2389
|
+
* Where this agent's files go. Resolution only: nothing is created and
|
|
2390
|
+
* nothing can fail that would not fail for a bad id, so a caller that
|
|
2391
|
+
* merely needs to *name* the path — the provenance ledger looking for a
|
|
2392
|
+
* file that may not exist — can ask without consequence.
|
|
2393
|
+
*/
|
|
2394
|
+
forAgent(agentId: string): string;
|
|
2395
|
+
/**
|
|
2396
|
+
* The same path, created and with its permissions settled, for a caller
|
|
2397
|
+
* about to write.
|
|
2398
|
+
*
|
|
2399
|
+
* The host does this rather than the plugin because the workspace sits
|
|
2400
|
+
* inside the agent's state directory, beside its sessions, memories and
|
|
2401
|
+
* grants — so the recursive `mkdir` a file-producing plugin would
|
|
2402
|
+
* otherwise reach for makes a *state* directory under the process umask.
|
|
2403
|
+
* A plugin has no way to know that; the host that chose the layout does.
|
|
2404
|
+
* Callers may still create subdirectories under what they are handed.
|
|
2405
|
+
*
|
|
2406
|
+
* Separate from {@link forAgent} because it can fail, and what fails
|
|
2407
|
+
* with it should be only what needed the directory. A read that wanted
|
|
2408
|
+
* a path, a navigation that writes nothing, a tool result that turned
|
|
2409
|
+
* out to be text — none of those should stop because a directory they
|
|
2410
|
+
* were never going to use could not be made.
|
|
2411
|
+
*/
|
|
2412
|
+
prepare(agentId: string): string;
|
|
2413
|
+
/**
|
|
2414
|
+
* Every agent workspace the host can currently name, for the guards that
|
|
2415
|
+
* have to recognise *any* agent's file rather than the caller's — the
|
|
2416
|
+
* filesystem provenance ledger is the one that does.
|
|
2417
|
+
*
|
|
2418
|
+
* Paths, not a promise that each exists: a caller canonicalizes and
|
|
2419
|
+
* tolerates what is missing. Re-read rather than cached, so an agent
|
|
2420
|
+
* added under a running daemon is visible to the next call.
|
|
2421
|
+
*/
|
|
2422
|
+
all(): Promise<readonly string[]>;
|
|
2423
|
+
}
|
|
1740
2424
|
export interface MemoryRegistrationHandle {
|
|
1741
2425
|
register(contribution: MemoryStoreContribution): void;
|
|
1742
2426
|
}
|
|
@@ -1774,6 +2458,19 @@ export interface PluginContext {
|
|
|
1774
2458
|
channels?: ChannelRegistrationHandle;
|
|
1775
2459
|
memory?: MemoryRegistrationHandle;
|
|
1776
2460
|
executors?: ExecutorRegistrationHandle;
|
|
2461
|
+
/**
|
|
2462
|
+
* Where this plugin may write files, per agent. See {@link AgentWorkspaces}.
|
|
2463
|
+
*
|
|
2464
|
+
* A host that omits it leaves every plugin that produces a file with
|
|
2465
|
+
* nowhere the host has chosen to put it. Such a plugin falls back to the
|
|
2466
|
+
* `workspaceRoot` config key — the documented root it joins the agent id
|
|
2467
|
+
* onto — and, given neither, must fail the call naming what is missing,
|
|
2468
|
+
* the way `browser.screenshot` does. Never a directory of its own
|
|
2469
|
+
* choosing: a file an agent was told it produced has to be somewhere the
|
|
2470
|
+
* operator can find, and a turn that cannot store its output must not
|
|
2471
|
+
* read as though it did.
|
|
2472
|
+
*/
|
|
2473
|
+
workspaces?: AgentWorkspaces;
|
|
1777
2474
|
/**
|
|
1778
2475
|
* The host's log, for what a plugin has to say after `setup` returns —
|
|
1779
2476
|
* a server that dropped, a reconnect that failed. The daemon's is the
|
|
@@ -1986,17 +2683,6 @@ export declare const renderPersonaSection: (agent: Pick<AgentDefinition, "name"
|
|
|
1986
2683
|
* reads a stranger's sentence as a stranger's.
|
|
1987
2684
|
*/
|
|
1988
2685
|
export declare const memoryRegionHeading: (trust: TrustLevel) => string;
|
|
1989
|
-
/**
|
|
1990
|
-
* The memory block, one region per trust label present, most trusted
|
|
1991
|
-
* first, entries in the order given within each.
|
|
1992
|
-
*
|
|
1993
|
-
* The invariant this enforces: nothing in the prompt derived from an entry
|
|
1994
|
-
* renders under a higher label than that entry carries. Stated over the
|
|
1995
|
-
* whole ordering rather than over `external` alone, because `unknown`
|
|
1996
|
-
* stopped being only a legacy artifact the moment an unauthorized sender's
|
|
1997
|
-
* message started producing it. An entry with no label renders in the
|
|
1998
|
-
* `unknown` region — never as the agent's own conclusion.
|
|
1999
|
-
*/
|
|
2000
2686
|
/**
|
|
2001
2687
|
* Unicode's `Bidi_Control` property as a character-class fragment: the
|
|
2002
2688
|
* marks, the Arabic letter mark, the embeddings and overrides, and the
|
|
@@ -2017,7 +2703,23 @@ export declare const BIDI_CONTROL_CHARACTERS = "\\u061c\\u200e\\u200f\\u202a-\\u
|
|
|
2017
2703
|
* shown is still what is stored.
|
|
2018
2704
|
*/
|
|
2019
2705
|
export declare const escapeControlCharacters: (text: string) => string;
|
|
2020
|
-
|
|
2706
|
+
/**
|
|
2707
|
+
* The memory block: the pinned core, the topic index, and the recency tail,
|
|
2708
|
+
* each grouped into trust regions, joined into **one** section.
|
|
2709
|
+
*
|
|
2710
|
+
* One section rather than three is a requirement rather than a formatting
|
|
2711
|
+
* preference. An adapter that places the volatile section separately finds
|
|
2712
|
+
* it with `parts.find((part) => part.kind === 'memory')` and strips it with
|
|
2713
|
+
* the matching filter, so three sections sharing the kind would send the
|
|
2714
|
+
* first to the tail and drop the other two from the request entirely —
|
|
2715
|
+
* and three new kinds would leave them on the cached prefix, which is
|
|
2716
|
+
* exactly what memory was moved off.
|
|
2717
|
+
*
|
|
2718
|
+
* A bare array is accepted and read as the recency tail alone: that is what
|
|
2719
|
+
* `memory` meant before the three blocks existed, and a provider test or a
|
|
2720
|
+
* host building a request by hand should not have to know about pinning.
|
|
2721
|
+
*/
|
|
2722
|
+
export declare const renderMemorySection: (memory: readonly MemoryEntry[] | MemoryInjection | undefined) => string | undefined;
|
|
2021
2723
|
/**
|
|
2022
2724
|
* The skills block: one line per enabled skill — id, name, and the
|
|
2023
2725
|
* description routing runs on. This is the whole point of the step: the
|
|
@@ -2209,6 +2911,78 @@ export interface ObserveInput {
|
|
|
2209
2911
|
*/
|
|
2210
2912
|
metadata?: JsonObject;
|
|
2211
2913
|
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Thrown by a provider when the request did not fit the model's context
|
|
2916
|
+
* window — the transcript, not the answer, is what was too long.
|
|
2917
|
+
*
|
|
2918
|
+
* A distinct type because it is the one provider failure the kernel can do
|
|
2919
|
+
* something about. Everything else a provider throws is final; this one
|
|
2920
|
+
* says "the same request will never work, a shorter one might", and the
|
|
2921
|
+
* runner answers by sending less history (see `raiseContextFloor`).
|
|
2922
|
+
*
|
|
2923
|
+
* Providers opt in. Recognising it means matching a vendor's own wording
|
|
2924
|
+
* for a 400, which only the adapter can do — and a harness provider that
|
|
2925
|
+
* manages its own context (`provider-claude-code`, `provider-codex`) has
|
|
2926
|
+
* nothing to report, because the transcript it sends is not ours to
|
|
2927
|
+
* shorten. A provider that never throws this behaves exactly as it did.
|
|
2928
|
+
*/
|
|
2929
|
+
export declare class ContextOverflowError extends Error {
|
|
2930
|
+
constructor(message: string);
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* How many of a session's oldest messages are no longer sent to the
|
|
2934
|
+
* provider.
|
|
2935
|
+
*
|
|
2936
|
+
* Durable, and **monotonic** like the trust label: a transcript only grows,
|
|
2937
|
+
* so a floor that has had to rise once would have to rise again on the next
|
|
2938
|
+
* turn, and re-discovering that costs a rejected request every time. Raised
|
|
2939
|
+
* only by `raiseContextFloor`, never lowered — a conversation does not get
|
|
2940
|
+
* shorter.
|
|
2941
|
+
*
|
|
2942
|
+
* It bounds what is *sent*, never what is stored. The transcript is the
|
|
2943
|
+
* record of what happened and stays whole on disk; this is a window onto
|
|
2944
|
+
* its tail. `stratus session rollover` is still the way to actually leave a
|
|
2945
|
+
* conversation behind.
|
|
2946
|
+
*/
|
|
2947
|
+
export declare const CONTEXT_FLOOR_METADATA_KEY = "contextFloor";
|
|
2948
|
+
/** The stored floor, or 0 for a session that has never overflowed. */
|
|
2949
|
+
export declare const contextFloorOf: (session: Pick<Session, "metadata">) => number;
|
|
2950
|
+
/**
|
|
2951
|
+
* The note that stands in for the messages a floor cut away, prefixed to
|
|
2952
|
+
* the oldest message the model still sees.
|
|
2953
|
+
*
|
|
2954
|
+
* Said rather than done silently, for the reason every other cut in this
|
|
2955
|
+
* codebase is announced: a model that cannot tell a conversation that
|
|
2956
|
+
* started here from one that was trimmed will answer as though the earlier
|
|
2957
|
+
* turns never happened, and confidently.
|
|
2958
|
+
*/
|
|
2959
|
+
export declare const droppedTurnsNote: (count: number) => string;
|
|
2960
|
+
/**
|
|
2961
|
+
* The messages above `session`'s floor, with the
|
|
2962
|
+
* note in place of what was cut. The session itself is untouched — the
|
|
2963
|
+
* copy is shallow and deliberately shares `metadata`, because providers
|
|
2964
|
+
* keep replay state there (the Anthropic raw-turn cache) and a turn that
|
|
2965
|
+
* wrote into a copy would lose it.
|
|
2966
|
+
*
|
|
2967
|
+
* Returns the session unchanged when the floor is 0, so a conversation that
|
|
2968
|
+
* has never overflowed is not copied on every turn.
|
|
2969
|
+
*/
|
|
2970
|
+
export declare const messagesWithinContextFloor: (session: Session) => readonly Message[];
|
|
2971
|
+
/**
|
|
2972
|
+
* Raise the floor so the next attempt sends less, and report whether there
|
|
2973
|
+
* was anything left to give up.
|
|
2974
|
+
*
|
|
2975
|
+
* Halves the remaining window each time rather than trimming a fixed
|
|
2976
|
+
* amount: nothing here knows how many tokens a message is, and an overflow
|
|
2977
|
+
* says only "too big", not "too big by this much" — so the cheap thing is
|
|
2978
|
+
* to converge in a handful of attempts rather than to creep towards the
|
|
2979
|
+
* answer one turn at a time, paying a rejected request for each step.
|
|
2980
|
+
*
|
|
2981
|
+
* Returns false when the window is already down to the newest turn. That is
|
|
2982
|
+
* the floor's floor: a single turn that does not fit is a message larger
|
|
2983
|
+
* than the model can read, and no amount of dropping history fixes it.
|
|
2984
|
+
*/
|
|
2985
|
+
export declare const raiseContextFloor: (session: Session) => boolean;
|
|
2212
2986
|
/**
|
|
2213
2987
|
* Thrown when a run is stopped by its abort signal. The session ends up
|
|
2214
2988
|
* `failed` with this error's message as `lastError`, so an aborted turn is
|
|
@@ -2272,6 +3046,18 @@ export interface AgentRunnerOptions {
|
|
|
2272
3046
|
*/
|
|
2273
3047
|
streaming?: boolean;
|
|
2274
3048
|
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Tool turns one message may take before it wraps up. The harness providers
|
|
3051
|
+
* spend the same number as their own inner budget, so they import it rather
|
|
3052
|
+
* than keep a copy.
|
|
3053
|
+
*
|
|
3054
|
+
* 8 until the wrap-up turn existed, and 8 was the whole budget for one Slack
|
|
3055
|
+
* message: an agent with a shell spent it on a handful of commands and
|
|
3056
|
+
* failed with nothing said. With a wrap-up turn the ceiling is where a long
|
|
3057
|
+
* task checks in rather than where it dies, and 40 is enough for real
|
|
3058
|
+
* multi-step work while still bounding what a runaway loop can spend.
|
|
3059
|
+
*/
|
|
3060
|
+
export declare const DEFAULT_MAX_TURNS = 40;
|
|
2275
3061
|
export declare class AgentRunner {
|
|
2276
3062
|
readonly bus: EventBus;
|
|
2277
3063
|
readonly store: SessionStore;
|