Task Agents
Task Agents
Autonomous agents with planning and memory
CheckpointManager class
📍 src/capabilities/taskAgent/CheckpointManager.ts:36
Manages state checkpointing for persistence and recovery
Constructor
constructor
constructor(storage: IAgentStorage, strategy: CheckpointStrategy = DEFAULT_CHECKPOINT_STRATEGY)
Parameters:
storage:IAgentStoragestrategy:CheckpointStrategy(optional) (default:DEFAULT_CHECKPOINT_STRATEGY)
Methods
setCurrentState()
Set the current agent state (for interval checkpointing)
setCurrentState(state: AgentState): void
Parameters:
state:AgentState
Returns: void
onToolCall()
Record a tool call (may trigger checkpoint)
async onToolCall(state: AgentState): Promise<void>
Parameters:
state:AgentState
Returns: Promise<void>
onLLMCall()
Record an LLM call (may trigger checkpoint)
async onLLMCall(state: AgentState): Promise<void>
Parameters:
state:AgentState
Returns: Promise<void>
checkpoint()
Force a checkpoint
async checkpoint(state: AgentState, reason: string): Promise<void>
Parameters:
state:AgentStatereason:string
Returns: Promise<void>
flush()
Wait for all pending checkpoints to complete
async flush(): Promise<void>
Returns: Promise<void>
cleanup()
Cleanup resources
async cleanup(): Promise<void>
Returns: Promise<void>
ExternalDependencyHandler class
📍 src/capabilities/taskAgent/ExternalDependencyHandler.ts:21
Handles external task dependencies
Constructor
constructor
constructor(tools: ToolFunction[] = [])
Parameters:
tools:ToolFunction<any, any>[](optional) (default:[])
Methods
startWaiting()
Start handling a task's external dependency
async startWaiting(task: Task): Promise<void>
Parameters:
task:Task
Returns: Promise<void>
stopWaiting()
Stop waiting on a task's external dependency
stopWaiting(task: Task): void
Parameters:
task:Task
Returns: void
triggerWebhook()
Trigger a webhook
async triggerWebhook(webhookId: string, data: unknown): Promise<void>
Parameters:
webhookId:stringdata:unknown
Returns: Promise<void>
completeManual()
Complete a manual task
async completeManual(taskId: string, data: unknown): Promise<void>
Parameters:
taskId:stringdata:unknown
Returns: Promise<void>
cleanup()
Cleanup all active dependencies
cleanup(): void
Returns: void
updateTools()
Update available tools
updateTools(tools: ToolFunction[]): void
Parameters:
tools:ToolFunction<any, any>[]
Returns: void
InContextMemoryPluginNextGen class
📍 src/core/context-nextgen/plugins/InContextMemoryPluginNextGen.ts:89
Constructor
constructor
constructor(config: InContextMemoryConfig = {})
Parameters:
config:InContextMemoryConfig(optional) (default:{})
Methods
getInstructions()
getInstructions(): string
Returns: string
getContent()
async getContent(): Promise<string | null>
Returns: Promise<string | null>
getContents()
getContents(): Map<string, InContextEntry>
Returns: Map<string, InContextEntry>
getTokenSize()
getTokenSize(): number
Returns: number
getInstructionsTokenSize()
getInstructionsTokenSize(): number
Returns: number
isCompactable()
isCompactable(): boolean
Returns: boolean
compact()
async compact(targetTokensToFree: number): Promise<number>
Parameters:
targetTokensToFree:number
Returns: Promise<number>
getTools()
getTools(): ToolFunction[]
Returns: ToolFunction<any, any>[]
destroy()
destroy(): void
Returns: void
getState()
getState(): SerializedInContextMemoryState
Returns: SerializedInContextMemoryState
restoreState()
restoreState(state: unknown): void
Parameters:
state:unknown
Returns: void
set()
Store or update a key-value pair
set(key: string, description: string, value: unknown, priority?: InContextPriority, showInUI?: boolean): void
Parameters:
key:stringdescription:stringvalue:unknownpriority:InContextPriority | undefined(optional)showInUI:boolean | undefined(optional)
Returns: void
get()
Get a value by key
get(key: string): unknown | undefined
Parameters:
key:string
Returns: unknown
has()
Check if a key exists
has(key: string): boolean
Parameters:
key:string
Returns: boolean
delete()
Delete an entry
delete(key: string): boolean
Parameters:
key:string
Returns: boolean
list()
List all entries with metadata
list(): Array<{ key: string; description: string; priority: InContextPriority; updatedAt: number; showInUI: boolean }>
Returns: { key: string; description: string; priority: InContextPriority; updatedAt: number; showInUI: boolean; }[]
clear()
Clear all entries
clear(): void
Returns: void
getStoreSchema()
getStoreSchema(): StoreEntrySchema
Returns: StoreEntrySchema
storeGet()
async storeGet(key?: string, _context?: ToolContext): Promise<StoreGetResult>
Parameters:
key:string | undefined(optional)_context:ToolContext | undefined(optional)
Returns: Promise<StoreGetResult>
storeSet()
async storeSet(key: string, data: Record<string, unknown>, _context?: ToolContext): Promise<StoreSetResult>
Parameters:
key:stringdata:Record<string, unknown>_context:ToolContext | undefined(optional)
Returns: Promise<StoreSetResult>
storeDelete()
async storeDelete(key: string, _context?: ToolContext): Promise<StoreDeleteResult>
Parameters:
key:string_context:ToolContext | undefined(optional)
Returns: Promise<StoreDeleteResult>
storeList()
async storeList(_filter?: Record<string, unknown>, _context?: ToolContext): Promise<StoreListResult>
Parameters:
_filter:Record<string, unknown> | undefined(optional)_context:ToolContext | undefined(optional)
Returns: Promise<StoreListResult>
Properties
| Property | Type | Description |
|---|---|---|
name |
name: "in_context_memory" |
- |
InMemoryAdapter class
📍 src/memory/adapters/inmemory/InMemoryAdapter.ts:55
Constructor
constructor
constructor(opts: InMemoryAdapterOptions = {})
Parameters:
opts:InMemoryAdapterOptions(optional) (default:{})
Methods
createEntity()
async createEntity(input: NewEntity): Promise<IEntity>
Parameters:
input:NewEntity
Returns: Promise<IEntity>
createEntities()
async createEntities(inputs: NewEntity[]): Promise<IEntity[]>
Parameters:
inputs:NewEntity[]
Returns: Promise<IEntity[]>
updateEntity()
async updateEntity(entity: IEntity): Promise<void>
Parameters:
entity:IEntity
Returns: Promise<void>
getEntity()
async getEntity(id: EntityId, scope: ScopeFilter): Promise<IEntity | null>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IEntity | null>
getEntities()
async getEntities(ids: EntityId[], scope: ScopeFilter): Promise<Array<IEntity | null>>
Parameters:
ids:string[]scope:ScopeFilter
Returns: Promise<(IEntity | null)[]>
findEntitiesByIdentifier()
async findEntitiesByIdentifier(
kind: string,
value: string,
scope: ScopeFilter,
): Promise<IEntity[]>
Parameters:
kind:stringvalue:stringscope:ScopeFilter
Returns: Promise<IEntity[]>
atomicCreateOrFindByNormalizedName()
Atomic find-or-create by (type, normalizedDisplayName, scope). See the
interface contract. JS event-loop atomicity: the body has a single
await (the createEntity call); the find-by-normalized phase runs
synchronously over the in-memory map, so two concurrent invocations
cannot both pass the find without one observing the other's insert.
async atomicCreateOrFindByNormalizedName(
input: NewEntity,
scope: ScopeFilter,
): Promise<{ entity: IEntity; created: boolean }>
Parameters:
input:NewEntityscope:ScopeFilter
Returns: Promise<{ entity: IEntity; created: boolean; }>
findEntitiesByNormalizedName()
async findEntitiesByNormalizedName(
type: string | undefined,
normalized: string,
scope: ScopeFilter,
opts?: { matchAliases?: boolean; limit?: number },
): Promise<IEntity[]>
Parameters:
type:string | undefinednormalized:stringscope:ScopeFilteropts:{ matchAliases?: boolean | undefined; limit?: number | undefined; } | undefined(optional)
Returns: Promise<IEntity[]>
searchEntities()
async searchEntities(
query: string,
opts: EntitySearchOptions,
scope: ScopeFilter,
): Promise<Page<IEntity>>
Parameters:
query:stringopts:EntitySearchOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
listEntities()
async listEntities(
filter: EntityListFilter,
opts: ListOptions,
scope: ScopeFilter,
): Promise<Page<IEntity>>
Parameters:
filter:EntityListFilteropts:ListOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
archiveEntity()
async archiveEntity(id: EntityId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
deleteEntity()
async deleteEntity(id: EntityId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
createFact()
async createFact(input: NewFact): Promise<IFact>
Parameters:
input:NewFact
Returns: Promise<IFact>
createFacts()
async createFacts(inputs: NewFact[]): Promise<IFact[]>
Parameters:
inputs:NewFact[]
Returns: Promise<IFact[]>
getFact()
async getFact(id: FactId, scope: ScopeFilter): Promise<IFact | null>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IFact | null>
findFacts()
async findFacts(
filter: FactFilter,
opts: FactQueryOptions,
scope: ScopeFilter,
): Promise<Page<IFact>>
Parameters:
filter:FactFilteropts:FactQueryOptionsscope:ScopeFilter
Returns: Promise<Page<IFact>>
updateFact()
async updateFact(id: FactId, patch: Partial<IFact>, scope: ScopeFilter): Promise<void>
Parameters:
id:stringpatch:Partial<IFact>scope:ScopeFilter
Returns: Promise<void>
rewriteEntityPrincipal()
Rewrite entity:<from> → entity:<to> access grants across stored
entities + facts. In-place mutation of the backing maps (reads return
clones, so callers never see partial state). Mirrors
MongoMemoryAdapter.rewriteEntityPrincipal — scoped to the caller's tenant
(scope.groupId) for parity + defense-in-depth.
async rewriteEntityPrincipal(
fromEntityId: EntityId,
toEntityId: EntityId,
scope: ScopeFilter,
): Promise<void>
Parameters:
fromEntityId:stringtoEntityId:stringscope:ScopeFilter
Returns: Promise<void>
countFacts()
async countFacts(filter: FactFilter, scope: ScopeFilter): Promise<number>
Parameters:
filter:FactFilterscope:ScopeFilter
Returns: Promise<number>
traverse()
async traverse(
startId: EntityId,
opts: TraversalOptions,
scope: ScopeFilter,
): Promise<Neighborhood>
Parameters:
startId:stringopts:TraversalOptionsscope:ScopeFilter
Returns: Promise<Neighborhood>
semanticSearch()
async semanticSearch(
queryVector: number[],
filter: FactFilter,
opts: SemanticSearchOptions,
scope: ScopeFilter,
): Promise<Array<{ fact: IFact; score: number }>>
Parameters:
queryVector:number[]filter:FactFilteropts:SemanticSearchOptionsscope:ScopeFilter
Returns: Promise<{ fact: IFact; score: number; }[]>
semanticSearchEntities()
async semanticSearchEntities(
queryVector: number[],
filter: EntitySemanticSearchFilter,
opts: SemanticSearchOptions & { minScore?: number; embeddingField?: EntityEmbeddingField },
scope: ScopeFilter,
): Promise<Array<{ entity: IEntity; score: number }>>
Parameters:
queryVector:number[]filter:EntitySemanticSearchFilteropts:SemanticSearchOptions & { minScore?: number | undefined; embeddingField?: EntityEmbeddingField | undefined; }scope:ScopeFilter
Returns: Promise<{ entity: IEntity; score: number; }[]>
destroy()
destroy(): void
Returns: void
InMemoryAgentStateStorage class
📍 src/infrastructure/storage/InMemoryStorage.ts:140
In-memory implementation of IAgentStateStorage
Methods
save()
async save(state: AgentState): Promise<void>
Parameters:
state:AgentState
Returns: Promise<void>
load()
async load(agentId: string): Promise<AgentState | undefined>
Parameters:
agentId:string
Returns: Promise<AgentState | undefined>
delete()
async delete(agentId: string): Promise<void>
Parameters:
agentId:string
Returns: Promise<void>
list()
async list(filter?: { status?: AgentStatus[] }): Promise<AgentState[]>
Parameters:
filter:{ status?: AgentStatus[] | undefined; } | undefined(optional)
Returns: Promise<AgentState[]>
patch()
async patch(agentId: string, updates: Partial<AgentState>): Promise<void>
Parameters:
agentId:stringupdates:Partial<AgentState>
Returns: Promise<void>
InMemoryEvidenceStorage class
📍 src/infrastructure/storage/EvidenceStorage.ts:202
Bounded process-local store. Share the instance explicitly when resuming in-process.
Methods
inventory()
protected async inventory(): Promise<Array<{ id: string; bytes: number }>>
Returns: Promise<{ id: string; bytes: number; }[]>
writeBlob()
protected async writeBlob(id: string, bytes: Buffer): Promise<void>
Parameters:
id:stringbytes:Buffer<ArrayBufferLike>
Returns: Promise<void>
readBlob()
protected async readBlob(id: string): Promise<Buffer | null>
Parameters:
id:string
Returns: Promise<Buffer<ArrayBufferLike> | null>
removeBlob()
protected async removeBlob(id: string): Promise<boolean>
Parameters:
id:string
Returns: Promise<boolean>
Properties
| Property | Type | Description |
|---|---|---|
durability |
durability: "process" |
- |
queueKey |
queueKey: ${string}-${string}-${string}-${string}-${string}`` |
- |
InMemoryHistoryStorage class
📍 src/infrastructure/storage/InMemoryHistoryStorage.ts:17
In-memory history storage implementation
Methods
addMessage()
async addMessage(message: HistoryMessage): Promise<void>
Parameters:
message:HistoryMessage
Returns: Promise<void>
getMessages()
async getMessages(): Promise<HistoryMessage[]>
Returns: Promise<HistoryMessage[]>
getRecentMessages()
async getRecentMessages(count: number): Promise<HistoryMessage[]>
Parameters:
count:number
Returns: Promise<HistoryMessage[]>
removeMessage()
async removeMessage(id: string): Promise<void>
Parameters:
id:string
Returns: Promise<void>
removeOlderThan()
async removeOlderThan(timestamp: number): Promise<number>
Parameters:
timestamp:number
Returns: Promise<number>
clear()
async clear(): Promise<void>
Returns: Promise<void>
getCount()
async getCount(): Promise<number>
Returns: Promise<number>
getState()
async getState(): Promise<SerializedHistoryState>
Returns: Promise<SerializedHistoryState>
restoreState()
async restoreState(state: SerializedHistoryState): Promise<void>
Parameters:
state:SerializedHistoryState
Returns: Promise<void>
InMemoryMetrics class
📍 src/infrastructure/observability/Metrics.ts:83
In-memory metrics aggregator (testing/development)
Methods
increment()
increment(metric: string, value: number = 1, tags?: MetricTags): void
Parameters:
metric:stringvalue:number(optional) (default:1)tags:MetricTags | undefined(optional)
Returns: void
gauge()
gauge(metric: string, value: number, tags?: MetricTags): void
Parameters:
metric:stringvalue:numbertags:MetricTags | undefined(optional)
Returns: void
timing()
timing(metric: string, duration: number, tags?: MetricTags): void
Parameters:
metric:stringduration:numbertags:MetricTags | undefined(optional)
Returns: void
histogram()
histogram(metric: string, value: number, tags?: MetricTags): void
Parameters:
metric:stringvalue:numbertags:MetricTags | undefined(optional)
Returns: void
getMetrics()
Get all metrics (for testing)
getMetrics(): {
counters: Map<string, number>;
gauges: Map<string, number>;
timings: Map<string, number[]>;
histograms: Map<string, number[]>;
}
Returns: { counters: Map<string, number>; gauges: Map<string, number>; timings: Map<string, number[]>; histograms: Map<string, number[]>; }
clear()
Clear all metrics
clear(): void
Returns: void
getTimingStats()
Get summary statistics for timings
getTimingStats(metric: string, tags?: MetricTags): {
count: number;
min: number;
max: number;
mean: number;
p50: number;
p95: number;
p99: number;
} | null
Parameters:
metric:stringtags:MetricTags | undefined(optional)
Returns: { count: number; min: number; max: number; mean: number; p50: number; p95: number; p99: number; } | null
InMemoryPlanStorage class
📍 src/infrastructure/storage/InMemoryStorage.ts:70
In-memory implementation of IPlanStorage
Methods
savePlan()
async savePlan(plan: Plan): Promise<void>
Parameters:
plan:Plan
Returns: Promise<void>
getPlan()
async getPlan(planId: string): Promise<Plan | undefined>
Parameters:
planId:string
Returns: Promise<Plan | undefined>
updateTask()
async updateTask(planId: string, task: Task): Promise<void>
Parameters:
planId:stringtask:Task
Returns: Promise<void>
addTask()
async addTask(planId: string, task: Task): Promise<void>
Parameters:
planId:stringtask:Task
Returns: Promise<void>
deletePlan()
async deletePlan(planId: string): Promise<void>
Parameters:
planId:string
Returns: Promise<void>
listPlans()
async listPlans(filter?: { status?: PlanStatus[] }): Promise<Plan[]>
Parameters:
filter:{ status?: PlanStatus[] | undefined; } | undefined(optional)
Returns: Promise<Plan[]>
findByWebhookId()
async findByWebhookId(webhookId: string): Promise<{ plan: Plan; task: Task } | undefined>
Parameters:
webhookId:string
Returns: Promise<{ plan: Plan; task: Task; } | undefined>
InMemoryStorage class
📍 src/infrastructure/storage/InMemoryStorage.ts:15
In-memory implementation of IMemoryStorage
Methods
get()
async get(key: string): Promise<MemoryEntry | undefined>
Parameters:
key:string
Returns: Promise<MemoryEntry | undefined>
set()
async set(key: string, entry: MemoryEntry): Promise<void>
Parameters:
key:stringentry:MemoryEntry
Returns: Promise<void>
delete()
async delete(key: string): Promise<void>
Parameters:
key:string
Returns: Promise<void>
has()
async has(key: string): Promise<boolean>
Parameters:
key:string
Returns: Promise<boolean>
getAll()
async getAll(): Promise<MemoryEntry[]>
Returns: Promise<MemoryEntry[]>
getByScope()
async getByScope(scope: MemoryScope): Promise<MemoryEntry[]>
Parameters:
scope:MemoryScope
Returns: Promise<MemoryEntry[]>
clearScope()
async clearScope(scope: MemoryScope): Promise<void>
Parameters:
scope:MemoryScope
Returns: Promise<void>
clear()
async clear(): Promise<void>
Returns: Promise<void>
getTotalSize()
async getTotalSize(): Promise<number>
Returns: Promise<number>
InvalidTaskTransitionError class
📍 src/memory/MemorySystem.ts:222
Thrown when transitionTaskState is called with validate: 'strict' and
the (from, to) pair is not allowed by the caller-supplied transition matrix.
Constructor
constructor
constructor(taskId: EntityId, from: string | undefined, to: string)
Parameters:
taskId:stringfrom:string | undefinedto:string
Properties
| Property | Type | Description |
|---|---|---|
from |
`from: string | undefined` |
to |
to: string |
- |
taskId |
taskId: string |
- |
MemoryConnectorStorage class
📍 src/connectors/storage/MemoryConnectorStorage.ts:20
Methods
save()
async save(name: string, stored: StoredConnectorConfig): Promise<void>
Parameters:
name:stringstored:StoredConnectorConfig
Returns: Promise<void>
get()
async get(name: string): Promise<StoredConnectorConfig | null>
Parameters:
name:string
Returns: Promise<StoredConnectorConfig | null>
delete()
async delete(name: string): Promise<boolean>
Parameters:
name:string
Returns: Promise<boolean>
has()
async has(name: string): Promise<boolean>
Parameters:
name:string
Returns: Promise<boolean>
list()
async list(): Promise<string[]>
Returns: Promise<string[]>
listAll()
async listAll(): Promise<StoredConnectorConfig[]>
Returns: Promise<StoredConnectorConfig[]>
clear()
Clear all stored configs (useful for testing)
clear(): void
Returns: void
size()
Get the number of stored configs
size(): number
Returns: number
MemoryEvictionCompactor class
📍 src/infrastructure/context/compactors/MemoryEvictionCompactor.ts:10
Constructor
constructor
constructor(private estimator: ITokenEstimator)
Parameters:
estimator:ITokenEstimator
Methods
canCompact()
canCompact(component: IContextComponent): boolean
Parameters:
component:IContextComponent
Returns: boolean
compact()
async compact(component: IContextComponent, targetTokens: number): Promise<IContextComponent>
Parameters:
component:IContextComponenttargetTokens:number
Returns: Promise<IContextComponent>
estimateSavings()
estimateSavings(component: IContextComponent): number
Parameters:
component:IContextComponent
Returns: number
Properties
| Property | Type | Description |
|---|---|---|
name |
name: "memory-eviction" |
- |
priority |
priority: 8 |
- |
MemoryPluginNextGen class
📍 src/core/context-nextgen/plugins/MemoryPluginNextGen.ts:361
Constructor
constructor
constructor(config: MemoryPluginConfig)
Parameters:
config:MemoryPluginConfig
Methods
getInstructions()
getInstructions(): string
Returns: string
prepare()
Force lazy bootstrap to complete.
The plugin resolves userEntityId / agentEntityId / groupEntityId
lazily on first getContent(). That's fine when the agent starts with
an LLM turn — getContent() runs as part of building the first prompt.
It's NOT fine when the agent starts with deterministic pre-steps (e.g.
routines: preSteps execute before the first LLM turn). A pre-step
invoking memory_recall({ subject: 'me' }) calls
getOwnSubjectIds().userEntityId — undefined → "no user scope" error.
prepare() is the contract for "make sure your lazy state is ready
before any tool you contribute is invoked". The routine runner calls it
once after building the agent and before pre-steps fire. Idempotent.
async prepare(): Promise<void>
Returns: Promise<void>
getContent()
async getContent(): Promise<string | null>
Returns: Promise<string | null>
getContents()
getContents(): unknown
Returns: unknown
getTokenSize()
getTokenSize(): number
Returns: number
getInstructionsTokenSize()
getInstructionsTokenSize(): number
Returns: number
isCompactable()
isCompactable(): boolean
Returns: boolean
compact()
async compact(_targetTokensToFree: number): Promise<number>
Parameters:
_targetTokensToFree:number
Returns: Promise<number>
getTools()
getTools(): ToolFunction[]
Returns: ToolFunction<any, any>[]
destroy()
destroy(): void
Returns: void
getState()
getState(): unknown
Returns: unknown
restoreState()
restoreState(state: unknown): void
Parameters:
state:unknown
Returns: void
getBootstrappedIds()
Entity IDs created (or resolved) during bootstrap. Undefined before bootstrap.
Always returns the variant agent entity id (keyed by system_agent_id),
even when a persona is configured — for "what was actually bootstrapped"
use this; for "what 'me' / 'this_agent' resolve to in tools" use
getOwnSubjectIds().
getBootstrappedIds(): {
userEntityId?: string;
agentEntityId?: string;
groupEntityId?: string;
}
Returns: { userEntityId?: string | undefined; agentEntityId?: string | undefined; groupEntityId?: string | undefined; }
getOwnSubjectIds()
Subject ids for LLM-facing 'me' / 'this_agent' token resolution,
used by both this plugin's read tools and (via AgentContextNextGen's
wiring) MemoryWritePluginNextGen's write tools. agentEntityId here
is persona-aware: if a personaEntityId was configured, this returns
the persona, so memory_set_agent_rule writes and rule-block reads share
one subject across every variant carrying the same persona.
Use getBootstrappedIds() instead when you need the variant agent entity
id (analytics, factContextIds, snapshots).
getOwnSubjectIds(): { userEntityId?: string; agentEntityId?: string }
Returns: { userEntityId?: string | undefined; agentEntityId?: string | undefined; }
Properties
| Property | Type | Description |
|---|---|---|
name |
name: "memory" |
- |
MemoryStorage class
📍 src/connectors/oauth/infrastructure/storage/MemoryStorage.ts:9
Methods
storeToken()
async storeToken(key: string, token: StoredToken): Promise<void>
Parameters:
key:stringtoken:StoredToken
Returns: Promise<void>
getToken()
async getToken(key: string): Promise<StoredToken | null>
Parameters:
key:string
Returns: Promise<StoredToken | null>
deleteToken()
async deleteToken(key: string): Promise<void>
Parameters:
key:string
Returns: Promise<void>
hasToken()
async hasToken(key: string): Promise<boolean>
Parameters:
key:string
Returns: Promise<boolean>
clearAll()
Clear all tokens (useful for testing)
clearAll(): void
Returns: void
size()
Get number of stored tokens
size(): number
Returns: number
listKeys()
List all storage keys (for account enumeration)
async listKeys(): Promise<string[]>
Returns: Promise<string[]>
MemorySystem class
📍 src/memory/MemorySystem.ts:304
Constructor
constructor
constructor(config: MemorySystemConfig)
Parameters:
config:MemorySystemConfig
Methods
upsertEntity()
async upsertEntity(
input: Partial<IEntity> & {
identifiers: Identifier[];
displayName: string;
type: string;
/**
* How `input.metadata` is folded into an existing entity on resolve.
* Default: undefined → metadata is ignored on resolve (current behavior,
* backward-compatible).
* - `'fillMissing'`: only set keys absent from stored metadata; existing
* values are never overwritten. Safe for LLM-driven re-extraction.
* - `'overwrite'`: shallow-merge — incoming keys win. Use when the
* caller is authoritative (calendar API for events, sync from system
* of record).
*
* On create (no match) all keys are set verbatim regardless of this option.
*/
metadataMerge?: 'fillMissing' | 'overwrite';
/**
* Optional whitelist applied when `metadataMerge` is set: only these
* top-level keys are touched. Other incoming keys are ignored. Lets the
* caller pin the merge to a known set (e.g. `['startTime','endTime',
* 'status']` for events) without leaking unrelated extracted fields.
*
* Has no effect when `metadataMerge` is unset.
*/
metadataMergeKeys?: string[];
},
scope: ScopeFilter,
options?: {
/**
* Allow a resolve-and-update to mutate an existing entity owned by a
* different user WITHIN THE SAME GROUP/TENANT. Default `false`.
*
* When `true`: if the best-match entity shares `scope.groupId`, the
* per-owner `assertCanAccess(write)` check is skipped — so trusted
* server-side consolidation (e.g. the signal pipeline converging a
* group-visible Person/Organization that another user in the same group
* created) can enrich it without re-owning it first. Mirrors the
* `allowCrossOwner` option on `mergeEntities`.
*
* Tenancy invariant is preserved: a cross-tenant best-match (different
* `groupId`) still goes through `assertCanAccess`, which denies it — the
* flag never enables cross-tenant writes. The host is responsible for
* gating which callers may pass it (server pipeline code only — never an
* agent/tool surface).
*/
allowCrossOwner?: boolean;
},
): Promise<UpsertEntityResult>
Parameters:
input:Partial<IEntity> & { identifiers: Identifier[]; displayName: string; type: string; metadataMerge?: "fillMissing" | "overwrite" | undefined; metadataMergeKeys?: string[] | undefined; }scope:ScopeFilteroptions:{ allowCrossOwner?: boolean | undefined; } | undefined(optional)
Returns: Promise<UpsertEntityResult>
getEntity()
getEntity(id: EntityId, scope: ScopeFilter): Promise<IEntity | null>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IEntity | null>
getEntities()
Batch fetch. Returned array aligns with ids positionally; missing ids /
scope-filtered-out entries become null. Intended for call sites that
need to resolve many EntityId references cheaply (e.g. rendering
fact.objectId as a displayName in the system message).
getEntities(ids: EntityId[], scope: ScopeFilter): Promise<Array<IEntity | null>>
Parameters:
ids:string[]scope:ScopeFilter
Returns: Promise<(IEntity | null)[]>
findEntitiesByIdentifier()
Return every entity visible at scope whose identifier list contains
(kind, value). Thin pass-through to the store — exposed so callers that
need to detect bootstrap duplicates (e.g. MemoryPluginNextGen) don't
have to reach into store directly.
findEntitiesByIdentifier(
kind: string,
value: string,
scope: ScopeFilter,
): Promise<IEntity[]>
Parameters:
kind:stringvalue:stringscope:ScopeFilter
Returns: Promise<IEntity[]>
searchEntities()
searchEntities(
query: string,
opts: { types?: string[]; limit?: number; cursor?: string },
scope: ScopeFilter,
)
Parameters:
query:stringopts:{ types?: string[] | undefined; limit?: number | undefined; cursor?: string | undefined; }scope:ScopeFilter
Returns: Promise<Page<IEntity>>
listEntities()
List entities by type + optional metadata equality filter. Thin pass-through
to the store's listEntities — exposed on MemorySystem so tool layers
don't need to reach into the store directly.
Read-side date coercion: ISO-8601 strings inside metadataFilter are
coerced to Date here (once, at the public boundary) so adapters always
see Date-typed values in range/equality clauses. Without it, callers (LLM
tools especially) writing the natural shape — { startTime: { $gte: "2026-05-14T00:00:00Z" } } — would silently get zero matches against
metadata.startTime (BSON Date), because Mongo and the InMemory comparator
treat String and Date as distinct types in range comparisons. Adapters do
not re-coerce — DRY, single point of responsibility across all backends.
listEntities(
filter: import('./types.js').EntityListFilter,
opts: import('./types.js').ListOptions,
scope: ScopeFilter,
)
Parameters:
filter:EntityListFilteropts:ListOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
findFacts()
Enumerate facts directly. Pass-through to the store's findFacts so tool
layers can list raw facts without reaching into the store. For ranked/
retrieval-oriented queries prefer getContext or semanticSearch.
findFacts(
filter: FactFilter,
opts: import('./types.js').FactQueryOptions,
scope: ScopeFilter,
)
Parameters:
filter:FactFilteropts:FactQueryOptionsscope:ScopeFilter
Returns: Promise<Page<IFact>>
getFact()
Fetch a single fact by id. Returns null when the fact does not exist or is not visible to the caller's scope.
getFact(id: FactId, scope: ScopeFilter)
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IFact | null>
resolveEntity()
Resolve a surface form ("Microsoft", "Q3 Planning", "John") to ranked
candidate entities. Matching tiers: strong identifier → exact displayName →
exact alias → fuzzy → semantic (identityEmbedding). Returns candidates
sorted by confidence; empty if nothing meets opts.threshold (default 0.5).
resolveEntity(
query: ResolveEntityQuery,
scope: ScopeFilter,
opts?: ResolveEntityOptions,
): Promise<EntityCandidate[]>
Parameters:
query:ResolveEntityQueryscope:ScopeFilteropts:ResolveEntityOptions | undefined(optional)
Returns: Promise<EntityCandidate[]>
upsertEntityBySurface()
Upsert-or-resolve by surface form. If the top candidate clears
autoResolveThreshold (default conservative 0.90), returns that entity
with the new surface + identifiers merged in (alias accumulation).
Otherwise creates a new entity and reports near-matches as
mergeCandidates for deferred human review.
async upsertEntityBySurface(
input: UpsertBySurfaceInput,
scope: ScopeFilter,
opts?: UpsertBySurfaceOptions,
): Promise<UpsertBySurfaceResult>
Parameters:
input:UpsertBySurfaceInputscope:ScopeFilteropts:UpsertBySurfaceOptions | undefined(optional)
Returns: Promise<UpsertBySurfaceResult>
mergeEntities()
Merge two entities — copy loser's identifiers + aliases onto winner, rewrite every fact whose subject or object is the loser to point at the winner, and archive the loser.
Scope-window limitation (defence-in-depth, by design): the rewrite step only touches facts visible to the caller's scope. Facts scoped more narrowly than the caller (e.g. other users' private facts on either entity) are left untouched and will continue to reference the archived loser. This prevents a user from rewriting data they cannot see, but means a "complete" merge requires a caller with broad-enough scope to see every referencing fact.
async mergeEntities(
winnerId: EntityId,
loserId: EntityId,
scope: ScopeFilter,
options?: {
/**
* Allow merging entities owned by different users within the same
* group/tenant. Default `false`.
*
* When `true`: the per-entity `assertCanAccess(write)` check is
* skipped (both winner and loser may be owned by users other than
* `scope.userId`), AND the same bypass is applied while rewriting
* fact references so the graph stays coherent post-merge.
*
* Tenancy invariant is preserved: the call refuses if
* `winner.groupId !== loser.groupId`. The host is responsible for
* gating which callers may pass the flag (typically a group/super
* admin running a legitimate consolidation pass).
*
* The owner of the surviving winner is unchanged either way — only
* the loser is archived, so its ownerId becomes irrelevant after.
*/
allowCrossOwner?: boolean;
},
): Promise<IEntity>
Parameters:
winnerId:stringloserId:stringscope:ScopeFilteroptions:{ allowCrossOwner?: boolean | undefined; } | undefined(optional)
Returns: Promise<IEntity>
setAccess()
Replace the explicit acl on an entity or fact and re-materialize its
readPrincipals/writePrincipals in place. The sanctioned exception to
fact-immutability for ACCESS changes (same category as the merge rewrite).
Caller must have write on the record.
Backs host grant/revoke surfaces. Returns the updated record.
async setAccess(
kind: 'entity' | 'fact',
id: string,
acl: ACLEntry[],
scope: ScopeFilter,
): Promise<IEntity | IFact>
Parameters:
kind:"entity" | "fact"id:stringacl:ACLEntry[]scope:ScopeFilter
Returns: Promise<IEntity | IFact>
archiveEntity()
Archive an entity (soft delete). Cascades to facts where the entity is
subjectId or objectId — those get archived too.
Tombstone semantics for contextIds (NO cascade): facts whose
contextIds array references this entity, and other entities whose
top-level contextIds references this one, are LEFT UNCHANGED. The
reference becomes a dangling pointer — readers resolving the id see
null (archived), the referencing record stays live. Symmetric across
fact-contextIds (long-standing behavior) and entity-contextIds.
Rationale: contextIds is a soft multi-anchor binding, not a hard
structural edge; force-removing it on archive would destructively
mutate consumer records the caller may not even own. Hosts that want
a stronger semantic should run a sweep after archive.
async archiveEntity(id: EntityId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
deleteEntity()
Delete an entity. Soft (default): same cascade rules as archiveEntity.
Hard (opts.hard: true): the entity row is removed; facts where it's
subject/object are archived. Same tombstone rule for contextIds:
contextIds references — fact-level and entity-level — are left as dangling
pointers. See archiveEntity JSDoc for rationale.
async deleteEntity(
id: EntityId,
scope: ScopeFilter,
opts: { hard?: boolean } = {},
): Promise<void>
Parameters:
id:stringscope:ScopeFilteropts:{ hard?: boolean | undefined; }(optional) (default:{})
Returns: Promise<void>
canonicalizePredicate()
Normalize a predicate string to its canonical form. When no registry is configured, returns the input unchanged.
Used by ExtractionResolver and available to external callers that want to pre-normalize predicates before querying.
canonicalizePredicate(input: string): string
Parameters:
input:string
Returns: string
hasPredicateRegistry()
True when a predicate registry is configured on this MemorySystem.
hasPredicateRegistry(): boolean
Returns: boolean
getPredicateRegistry()
Return the configured predicate registry, or undefined when none is
set. Exposed so prompt-rendering layers (e.g. MemoryWritePluginNextGen)
can generate LLM-facing predicate vocabulary from the same source of
truth — avoids hand-maintained predicate lists drifting out of sync
with what the storage layer actually accepts and ranks.
getPredicateRegistry(): PredicateRegistry | undefined
Returns: PredicateRegistry | undefined
ensureAdapterIndexes()
H7: ensure the configured adapter has all recommended indexes. No-op for
adapters that don't expose ensureIndexes (InMemoryAdapter has nothing
to index). Delegates to the adapter's own method — typically
MongoMemoryAdapter.ensureIndexes(). Idempotent.
Call from your migration system, not from application hot paths. Index creation on production collections can be expensive; the library intentionally does not call this automatically at construction time.
See docs/MEMORY_PERMISSIONS.md (or the memory README) for the full
list of recommended indexes and why each matters. Cross-process
deployments with MemoryPluginNextGen additionally need a unique index on
{identifiers.kind: 1, identifiers.value: 1} (partial, filtered on
identifiers.$.value: {$exists: true}) — this one is not created by
ensureIndexes() because adding a unique index to a collection with
existing duplicates fails hard; callers should build + verify it
explicitly in a migration.
async ensureAdapterIndexes(): Promise<void>
Returns: Promise<void>
assertEnterpriseReady()
Surface configuration drift that would make this MemorySystem unsafe for
enterprise multi-tenant deployment. Returns a list of human-readable
failings rather than throwing — hosts gate startup on findings.length.
Two readiness tiers — ok is not "fully enterprise ready":
ok === true→ the MINIMUM-SAFETY tier passed: data integrity invariants are held + tenant isolation is enforceable. Noseverity: 'error'findings remain. The host can boot without risking cross-tenant data leaks, owner-required violations, or permission-denial bypasses, but the LLM-writer ergonomics may still be lax.ok === trueANDfindings.every(f => f.severity !== 'warn')→ the FULL enterprise-readiness tier. Adds the strict-vocabulary guarantee (LLM writers can't invent predicates), enables Mongo's fast paths for vector search, and confirms the adapter is fully tuned. This is what production deployments should actually gate on.
Operators who want the stricter gate:
const { ok, findings } = memory.assertEnterpriseReady();
const enterpriseReady = ok && findings.every((f) => f.severity !== 'warn');
if (!enterpriseReady) throw new Error(`Memory not fully ready: ${JSON.stringify(findings)}`);
Severity rationale:
'error'is reserved for invariant violations — config that lets bad data into the store or leaks across tenants (novisibilityPolicy, noPredicateRegistryat all).'warn'covers quality / ergonomics — config that hurts retrieval, consistency, or LLM-write hygiene but doesn't compromise integrity (permissive predicate mode, missing vector index names).
What's checked:
- error —
PredicateRegistryis configured. Without one, predicates remain free-form strings; fragmentation is guaranteed under any LLM-driven write path. - error —
visibilityPolicyis set. Library defaults grant world-read to every entity/fact (644-style) — unsafe by default for enterprise. A policy that returns{ world: 'none' }(private) or{ world: 'none', group: 'read' }(group-readable) is the typical enterprise baseline. - warn —
predicateMode === 'strict'. Permissive mode + LLM writers fragments the predicate vocabulary over time (prefersvsprefervspreference), eroding dedup and ranking quality. Not an error because the data still has owners + permissions; only the vocabulary hygiene is at risk. - warn — Mongo adapter:
vectorIndexNameis set (drives Atlas Vector Search fast paths). Skipped for InMemoryAdapter. - warn — Mongo adapter + embedder present:
entityContentVectorIndexNameis set. Without itfindSimilarOpenTasks, document search, and any composer-driven retrieval fall back to the slow path.
What's NOT checked (require side effects the library shouldn't trigger from a readonly probe):
- Whether the Mongo unique partial index on
{identifiers.kind, identifiers.value}actually exists. Run a migration-side probe (db.entities.getIndexes()). - Whether the unique normalized-name partial index from
ensureNormalizedNameUniqueIndexexists. Same — migration-side. - Whether
ensureVectorSearchIndexes()has been called against Atlas. Idempotent to call again from your migration; that's the supported path.
assertEnterpriseReady(): {
ok: boolean;
findings: Array<{ severity: 'error' | 'warn'; code: string; message: string }>;
}
Returns: { ok: boolean; findings: { severity: "error" | "warn"; code: string; message: string; }[]; }
backfillNormalizedFields()
Backfill normalizedDisplayName + normalizedAliases on entities written
before v0.8.0 (or by adapter paths that bypassed the storage-boundary
stamp). Iterates entities in scope in batches, recomputes the fields
from each entity's current displayName / aliases, and writes when the
stored value differs.
Idempotent — running twice with the same data is a no-op. Run before
ensureNormalizedNameUniqueIndex (the unique partial index would fail
to build if pre-backfill dups exist).
Returns { scanned, updated, skipped } for caller observability:
scanned— total entities visited.updated— wrote an entity because at least one field differed.skipped— entity already had both fields populated and consistent; no write performed.
Concurrency caveat: the backfill uses updateEntity (optimistic
concurrency on version). Concurrent writers bumping the version
between read and write trigger a retry once; further conflicts surface
as OptimisticConcurrencyError on the helper.
Force mode: pass { force: true } to write every scanned entity
regardless of whether the stored value differs. Useful when
normalizeSurface has been tweaked and the entire population needs
re-stamping. Default is the cheaper differ-only path.
async backfillNormalizedFields(
scope: ScopeFilter,
opts?: { batchSize?: number; force?: boolean },
): Promise<{ scanned: number; updated: number; skipped: number }>
Parameters:
scope:ScopeFilteropts:{ batchSize?: number | undefined; force?: boolean | undefined; } | undefined(optional)
Returns: Promise<{ scanned: number; updated: number; skipped: number; }>
backfillAccessPrincipals()
Backfill readPrincipals / writePrincipals on every entity + fact (live
AND archived) written before the principal model — or by any path that
predates the storage-boundary stamp. Recomputes each row's arrays from its
ownerId / groupId / permissions / acl and writes when they differ.
THE MIGRATION GATE: a host MUST run this to completion before it starts
passing scope.principals. Query-time (Mongo) filtering has NO legacy
fallback for rows lacking readPrincipals — an un-backfilled row is simply
invisible to a principal caller (see ScopeFilter.principals).
Idempotent — re-running with the same data is a no-op (arrays are recomputed
deterministically and compared before writing). { force: true } rewrites
every row regardless (use after the materialization rules change).
Entities go through updateEntity (the storage boundary re-stamps the
arrays) with the same retry-once optimistic-concurrency handling as
backfillNormalizedFields. Facts are patched directly with the computed
arrays (no updateFact recompute is triggered — the patch carries the
arrays, not the access-source fields).
async backfillAccessPrincipals(
scope: ScopeFilter,
opts?: { batchSize?: number; force?: boolean },
): Promise<{
entitiesScanned: number;
entitiesUpdated: number;
factsScanned: number;
factsUpdated: number;
}>
Parameters:
scope:ScopeFilteropts:{ batchSize?: number | undefined; force?: boolean | undefined; } | undefined(optional)
Returns: Promise<{ entitiesScanned: number; entitiesUpdated: number; factsScanned: number; factsUpdated: number; }>
hoistContextIdsFromFactsToEntities()
Migration helper — for every fact with the given predicate, hoist the
fact's contextIds array onto the target entity's contextIds (union
merge). Target entity is the fact's subject or object depending on
entitySide.
Reference recipe — hoist legacy committed_to(person, task) linkage onto
task entities (the v10-era pattern that v11+ writes natively as
task.contextIds):
await memory.hoistContextIdsFromFactsToEntities({
predicate: 'committed_to',
entitySide: 'object',
entityType: 'task',
archiveSource: false, // keep originals as provenance until host confirms
}, scope);
Idempotent — addEntityContextIds deduplicates against existing values.
Safe to re-run. Skips facts with empty contextIds, missing target id,
or where the target entity type doesn't match entityType.
Performance. O(facts-with-this-predicate). Pass batchSize to tune
memory pressure vs round-trips (default 200, hard ceiling 1000).
Source archival. When archiveSource: true, the source fact is
archived after the hoist succeeds for that fact. Defaults to false
because hosts often want to keep the original fact for provenance — the
migration is informational, not destructive.
async hoistContextIdsFromFactsToEntities(
opts: {
predicate: string;
entitySide: 'subject' | 'object';
entityType?: string;
batchSize?: number;
dryRun?: boolean;
archiveSource?: boolean;
},
scope: ScopeFilter,
): Promise<{
scannedFacts: number;
/** Facts that triggered a real entity write (added >= 1 new contextId). */
hoistedEntities: number;
/**
* Facts where the target entity already had every contextId — no write
* performed. Important for migration planning: re-runs of an idempotent
* migration are expected to land entirely in this bucket. Also bumped on
* dry-run when the simulated hoist would have been a no-op.
*/
skippedAlreadyHoisted: number;
skippedNoContextIds: number;
skippedWrongType: number;
skippedNoTarget: number;
archivedFacts: number;
errors: number;
}>
Parameters:
opts:{ predicate: string; entitySide: "object" | "subject"; entityType?: string | undefined; batchSize?: number | undefined; dryRun?: boolean | undefined; archiveSource?: boolean | undefined; }scope:ScopeFilter
Returns: Promise<{ scannedFacts: number; hoistedEntities: number; skippedAlreadyHoisted: number; skippedNoContextIds: number; skippedWrongType: number; skippedNoTarget: number; archivedFacts: number; errors: number; }>
getPredicateDefinition()
Lookup a predicate definition (by canonical name or alias). Null when no registry or unknown.
getPredicateDefinition(nameOrAlias: string): PredicateDefinition | null
Parameters:
nameOrAlias:string
Returns: PredicateDefinition | null
resolveUnknownPredicate()
H5: the configured drift policy + closest known predicate for an unknown.
Returns { policy: 'fuzzy_map', mappedTo? } / { policy: 'keep' } /
{ policy: 'drop' }. mappedTo is only present when the policy is
'fuzzy_map' AND a close registry match exists. Intended for callers
(ExtractionResolver) that want to apply the policy themselves and record
the decision in their result payload.
resolveUnknownPredicate(canonical: string): {
policy: 'fuzzy_map' | 'keep' | 'drop';
mappedTo?: string;
distance?: number;
}
Parameters:
canonical:string
Returns: { policy: "fuzzy_map" | "keep" | "drop"; mappedTo?: string | undefined; distance?: number | undefined; }
addFact()
async addFact(
input: Partial<IFact> & {
subjectId: EntityId;
predicate: string;
kind: IFact['kind'];
/**
* When true: before inserting, look for a non-archived fact with the same
* (subjectId, canonicalized predicate, kind) and matching (value, objectId).
* On match → bump `observedAt` on the existing fact and return it — NO new
* row is inserted. Used by the session ingestor to prevent bloat across
* repeated observations ("anton works_at everworker" re-extracted).
* Details merging is the caller's responsibility (see updateFactDetails).
* Defaults to false for backward-compatibility.
*/
dedup?: boolean;
},
scope: ScopeFilter,
): Promise<IFact>
Parameters:
input:Partial<IFact> & { subjectId: string; predicate: string; kind: FactKind; dedup?: boolean | undefined; }scope:ScopeFilter
Returns: Promise<IFact>
addFacts()
async addFacts(
inputs: Array<
Partial<IFact> & { subjectId: EntityId; predicate: string; kind: IFact['kind'] }
>,
scope: ScopeFilter,
): Promise<IFact[]>
Parameters:
inputs:(Partial<IFact> & { subjectId: string; predicate: string; kind: FactKind; })[]scope:ScopeFilter
Returns: Promise<IFact[]>
supersedeFact()
supersedeFact(
oldId: FactId,
newInput: Partial<IFact> & { predicate: string; kind: IFact['kind']; subjectId: EntityId },
scope: ScopeFilter,
): Promise<IFact>
Parameters:
oldId:stringnewInput:Partial<IFact> & { predicate: string; kind: FactKind; subjectId: string; }scope:ScopeFilter
Returns: Promise<IFact>
archiveFact()
async archiveFact(id: FactId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
expireFacts()
Scheduled lifecycle sweep — archives facts whose validUntil is in the
past relative to asOf (defaults to "now"). Host applications call this
on a daily cadence to keep the visible graph trimmed.
Contract:
- Only affects facts that already have
validUntilset. Facts withoutvalidUntilare valid forever and never touched. - Only archives facts that are NOT already archived.
- Archived facts remain queryable via
archivedOnly/includeArchivedpaths — this is soft archive, not hard delete. - Scope-bounded: the caller's scope determines which facts are visible. Multi-tenant schedulers iterate over scopes and call once per scope.
- Emits BOTH
fact.archiveandfact.expireper archived fact. Thefact.archiveevent preserves the "any archival" contract that existing subscribers (cache invalidators, audit appenders) depend on;fact.expireis the lifecycle-specific signal carrying predicate + validUntil for observability that wants to distinguish automatic expiry from operator/agent archives.
Returns the count of facts archived in this sweep. Paginated internally to bound memory; pageSize defaults to 500.
async expireFacts(
opts: {
/** Cutoff. Facts with `validUntil < asOf` are eligible. Defaults to `new Date()`. */
asOf?: Date;
/**
* Restrict to specific predicates. When omitted, all expired facts in
* scope are archived. Pass `[]` to short-circuit to 0 (no-op).
*/
predicates?: string[];
/** Maximum number of facts to archive in this call. Defaults to unbounded. */
limit?: number;
/** Internal page size for findFacts. Defaults to 500. */
pageSize?: number;
},
scope: ScopeFilter,
): Promise<{ archived: number }>
Parameters:
opts:{ asOf?: Date | undefined; predicates?: string[] | undefined; limit?: number | undefined; pageSize?: number | undefined; }scope:ScopeFilter
Returns: Promise<{ archived: number; }>
restoreFact()
Reverse of archiveFact — restore a previously-archived fact so it
participates in queries again. Used by the memory_restore tool to give
agents an undo path for mistaken archives.
F1 guard: if the target fact was archived as part of a supersession
(i.e. another non-archived fact has supersedes: targetId), restoring it
would create two "current" facts for the same (subject, predicate) pair.
Throws FactSupersededError with the successor's id so callers can
handle the successor first. The tool layer surfaces this as a structured
error to the LLM.
async restoreFact(id: FactId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
updateFactDetails()
Update an existing fact's details field in place. Intended for merging
narrative context when the session ingestor finds a duplicate fact and
produces an LLM-merged details string. Recomputes isSemantic (the merged
text may cross the length threshold), clears the stale embedding, and
re-embeds if an embedder is configured.
This mutates the fact — the prior details is lost. Use supersession if
you need the full audit chain.
async updateFactDetails(
id: FactId,
details: string,
scope: ScopeFilter,
): Promise<IFact>
Parameters:
id:stringdetails:stringscope:ScopeFilter
Returns: Promise<IFact>
applyEntityReconciliationOps()
Apply entity-anchored reconciliation operations (Pillar 2).
Caller has:
- Built an
entityReconciliationPromptfor ONE entity's non-archived facts. - Run that prompt through an extractor (LLM call).
- Parsed the LLM output into
ReconciliationOp[]viaparseExtractionWithStatus.
This method dispatches the resulting ops against the fact graph:
update→updateFact(value / details / observedAt patch).archive→archiveFact.createops are REJECTED (counted asrejectedHallucinatedin the outcome) — Pillar 2 only resolves, never creates.
factId validation: every update/archive op's factId MUST appear in
priorFacts. Hallucinations are rejected with a warn-level log entry.
Returns op-level counts so callers can log decisions + emit activity-log
entries (per feedback_log_every_decision).
async applyEntityReconciliationOps(
ops: ReconciliationOp[],
priorFacts: IFact[],
scope: ScopeFilter,
opts?: {
/** Optional skeptic filter — return false to drop an op with logging. */
skepticFilter?: (op: ReconciliationOp) => boolean;
/**
* Source signal id to stamp on `updateFact` patches. Optional — entity
* reconciliation isn't bound to a single source signal (that's the
* point), so callers may leave it undefined.
*/
sourceSignalId?: string;
},
): Promise<OperationOutcome>
Parameters:
ops:ReconciliationOp[]priorFacts:IFact[]scope:ScopeFilteropts:{ skepticFilter?: ((op: ReconciliationOp) => boolean) | undefined; sourceSignalId?: string | undefined; } | undefined(optional)
Returns: Promise<OperationOutcome>
applyReconciliationOps()
Apply signal-reconciliation operations (the SECOND pass — see
signalReconciliationPrompt). Generic over signal source: the caller has
extracted NEW facts/tasks in pass 1, loaded the PRIOR facts/tasks
(deterministic + semantic, de-duplicated), run signalReconciliationPrompt,
and parsed the output via parseSignalReconciliationOpsWithStatus.
Dispatch:
- FACT ops are delegated to {@link applyEntityReconciliationOps} —
archivesupersedes,updatemutates in place,createis rejected (the pass reconciles, it does not create — pass 1 owns creation). Every fact op'sfactIdis validated againstcontext.priorFacts. task_updateops mutate the task in place:newStateroutes through {@link transitionTaskState} (state +stateHistory+completedAt), andnarrative/dueAt/assigneeIdpatch the metadata. EverytaskIdis validated againstcontext.priorTasks; hallucinated ids are rejected. WhennewStatemoves the task into a TERMINAL state, the task metadata is stamped with AI-resolution provenance (aiResolved,aiResolutionReason,aiResolutionEvidenceQuote,aiResolvedAt) so the host can distinguish AI auto-resolution from user action.
Returns op-level counts (fact creates/updates/archives + task updates/resolves
- rejections) so callers can log every decision per
feedback_log_every_decision.
async applyReconciliationOps(
ops: SignalReconciliationOp[],
context: { priorFacts: IFact[]; priorTasks: IEntity[] },
scope: ScopeFilter,
opts?: {
/** Optional skeptic filter — return false to drop an op with logging. */
skepticFilter?: (op: SignalReconciliationOp) => boolean;
/** Source signal id stamped on fact-update patches + task state history. */
sourceSignalId?: string;
/** Transition timestamp. Defaults to now. */
at?: Date;
},
): Promise<SignalReconciliationOutcome>
Parameters:
ops:SignalReconciliationOp[]context:{ priorFacts: IFact[]; priorTasks: IEntity[]; }scope:ScopeFilteropts:{ skepticFilter?: ((op: SignalReconciliationOp) => boolean) | undefined; sourceSignalId?: string | undefined; at?: Date | undefined; } | undefined(optional)
Returns: Promise<SignalReconciliationOutcome>
updateFact()
Mutate a fact in-place. Used by per-conversation reconciliation
(ExtractionResolver) to update value / details / observedAt /
sourceSignalId / evidenceQuote on a prior fact when the LLM emits
an update op — replacing the legacy "supersede chain" pattern that
left tombstones behind.
Restrictions:
- Cannot change
subjectId,predicate,kind,objectId,id,createdAt,archived(usearchiveFact/restoreFact). - Caller's scope must have write access to the fact.
Side effects:
- When
detailschanges, re-computesisSemantic, clears staleembedding+summaryForEmbedding, and re-embeds on the embedder queue (best-effort).
async updateFact(
id: FactId,
patch: Partial<
Pick<
IFact,
| 'value'
| 'details'
| 'observedAt'
| 'sourceSignalId'
| 'evidenceQuote'
| 'confidence'
| 'importance'
| 'metadata'
>
>,
scope: ScopeFilter,
): Promise<IFact>
Parameters:
id:stringpatch:Partial<Pick<IFact, "metadata" | "value" | "details" | "confidence" | "evidenceQuote" | "sourceSignalId" | "importance" | "observedAt">>scope:ScopeFilter
Returns: Promise<IFact>
findDuplicateFact()
Find an existing non-archived fact matching the (subjectId, predicate, kind, value, objectId) signature of input. Returns null on no match.
Exposed for callers (the session ingestor) that need to split insert vs merge paths themselves — e.g. to batch LLM-based details merging across several duplicates rather than merging one at a time.
Predicate is canonicalized via the registry (if one is configured) so
aliases match the same way addFact would treat them.
async findDuplicateFact(
input: Partial<IFact> & {
subjectId: EntityId;
predicate: string;
kind: IFact['kind'];
},
scope: ScopeFilter,
): Promise<IFact | null>
Parameters:
input:Partial<IFact> & { subjectId: string; predicate: string; kind: FactKind; }scope:ScopeFilter
Returns: Promise<IFact | null>
getContext()
async getContext(
entityId: EntityId,
opts: ContextOptions,
scope: ScopeFilter,
): Promise<EntityView>
Parameters:
entityId:stringopts:ContextOptionsscope:ScopeFilter
Returns: Promise<EntityView>
resolveRelatedItems()
Tasks related to a subject entity. Finds task entities (type='task') where any of the common relational metadata fields point at the subject, OR where a relational fact ties a task to the subject. Returns only non- terminal states by default.
async resolveRelatedItems(
entityIds: EntityId[],
scope: ScopeFilter,
opts?: {
types?: ('task' | 'event')[];
/** Task state filter. Default: configured `taskStates.active`. Pass empty array to disable filtering. */
taskStates?: string[];
/** Per-bucket cap (tasks and events each capped at this value). Default 50, hard ceiling 200. */
limit?: number;
asOf?: Date;
recentEventsWindowDays?: number;
},
): Promise<RelatedItemsResult>
Parameters:
entityIds:string[]scope:ScopeFilteropts:{ types?: ("task" | "event")[] | undefined; taskStates?: string[] | undefined; limit?: number | undefined; asOf?: Date | undefined; recentEventsWindowDays?: number | undefined; } | undefined(optional)
Returns: Promise<RelatedItemsResult>
findSimilarOpenTasks()
Semantic kNN over open tasks. Embeds queryText and ranks active tasks
by similarity against their contentEmbedding — the metadata-rich
composed text (state, due date, assignee/project displayNames,
description, contextIds — see taskContentComposer in
composers/defaults.ts). This is what lets generic-title tasks like
"Follow up" rank correctly when 50 tasks share a name but differ on
assignee/project/dueAt.
Pushes the task-state filter into the vector pipeline via
EntitySemanticSearchFilter.states when configured filter paths support
it (metadata.state in ENTITIES_FILTER_PATHS on Mongo Atlas). The
post-filter loop is kept as belt-and-suspenders for adapters that
silently ignore unknown filter fields.
Returns empty array when no embedder/semantic adapter is configured — callers should treat semantic similarity as opportunistic, not load-bearing.
async findSimilarOpenTasks(
queryOrVector: string | number[],
scope: ScopeFilter,
opts?: {
topK?: number;
minScore?: number;
taskStates?: string[];
/**
* Narrow the search to tasks whose top-level `contextIds` includes this
* entity id. Pushed into the underlying `semanticSearchEntities` filter
* → Atlas `$vectorSearch.filter` clause. Requires `'contextIds'` in the
* entities vector index's filter paths (declared by default — see
* `ENTITIES_FILTER_PATHS` in MongoMemoryAdapter).
*/
contextId?: EntityId;
/**
* Narrow by assignee entity id. Pushed into vector pipeline filter via
* `metadata.assigneeId` path. Useful for "tasks for Alice similar to X"
* scoped queries.
*/
assigneeId?: EntityId;
/**
* Narrow by project entity id. Pushed into vector pipeline filter via
* `metadata.projectId` path.
*/
projectId?: EntityId;
/**
* Narrow by reporter entity id. Pushed into vector pipeline filter via
* `metadata.reporterId` path. Mirrors `assigneeId`. Useful for surgical
* "tasks where X is reporter" queries — e.g. "what did I ask Y to do?".
*/
reporterId?: EntityId;
/**
* OR-wildcard role match — narrows to tasks where this entity appears
* in any of `metadata.assigneeId`, `metadata.reporterId`,
* `metadata.projectId`, or top-level `contextIds`. The entity-side
* analog of `FactFilter.touchesEntity`.
*
* Prefer this over fanning out N separate role-narrowed searches and
* unioning client-side: the union is pushed into Atlas
* `$vectorSearch.filter` as one compound `$or`, so vector ranking sees
* the participant-touching candidates together rather than competing
* across role-specific top-K cuts.
*/
touchesEntity?: EntityId;
/**
* Multi-anchor variant of `touchesEntity` — narrows to tasks touched by
* ANY id in this array (any of `assigneeId`/`reporterId`/`projectId`/
* `contextIds`). Use for participant fan-out: pass every participant +
* context-anchor entity id and let ONE ranked vector query surface the
* tasks relevant to the whole signal, instead of N per-anchor searches
* unioned client-side. ANDs with `touchesEntity` when both are set.
*/
touchesAnyOf?: EntityId[];
},
): Promise<Array<{ task: IEntity; score: number }>>
Parameters:
queryOrVector:string | number[]scope:ScopeFilteropts:{ topK?: number | undefined; minScore?: number | undefined; taskStates?: string[] | undefined; contextId?: string | undefined; assigneeId?: string | undefined; projectId?: string | undefined; reporterId?: string | undefined; touchesEntity?: string | undefined; touchesAnyOf?: string[] | undefined; } | undefined(optional)
Returns: Promise<{ task: IEntity; score: number; }[]>
semanticSearchEntities()
General-purpose entity-level semantic search over the content embedding
(IEntity.contentEmbedding, populated by the registered per-type content
composers). The type-agnostic analog of findSimilarOpenTasks (task-only)
and searchDocuments (document-only): rank ANY entity type by semantic
similarity to a query, narrowed by the full EntitySemanticSearchFilter
set (type(s), contextId, task-role fields, and the single/multi
touchesEntity / touchesAnyOf OR-wildcards).
Why it exists: hosts building signal-time retrieval ("which entities is
this email about?") previously had to fall back to fact-level
semanticSearch + manual subject/object/contextIds aggregation. This
exposes the entity content vector directly, so the composed entity text
(display name, identifiers, narrative, contextIds, host-specific fields)
is what gets matched — strictly better recall than re-deriving entities
from fact hits.
Accepts a precomputed vector (from embedQuery) to skip the per-call
embed round-trip when fanning out several searches over one query.
Error contract mirrors findSimilarOpenTasks: returns [] when the store
lacks a semantic-entity capability, when a string query is passed but no
embedder is configured, or when the underlying search throws. Semantic
similarity is opportunistic, never load-bearing.
embeddingField defaults to 'content' (the composer-backed vector). Pass
'identity' to search the name/identifier vector used by entity
resolution. Adapters MUST NOT silently fall back to the other field —
entities lacking the selected vector are simply absent from results.
async semanticSearchEntities(
queryOrVector: string | number[],
filter: EntitySemanticSearchFilter,
scope: ScopeFilter,
opts?: { topK?: number; minScore?: number; embeddingField?: EntityEmbeddingField },
): Promise<Array<{ entity: IEntity; score: number }>>
Parameters:
queryOrVector:string | number[]filter:EntitySemanticSearchFilterscope:ScopeFilteropts:{ topK?: number | undefined; minScore?: number | undefined; embeddingField?: EntityEmbeddingField | undefined; } | undefined(optional)
Returns: Promise<{ entity: IEntity; score: number; }[]>
updateEntityMetadata()
Shallow-merge a patch into entity.metadata. Version-bumping, scope-checked, emits entity.upsert event. Caller does NOT read-modify-write — this helper handles all of that atomically (from the caller's perspective).
async updateEntityMetadata(
id: EntityId,
patch: Record<string, unknown>,
scope: ScopeFilter,
): Promise<IEntity>
Parameters:
id:stringpatch:Record<string, unknown>scope:ScopeFilter
Returns: Promise<IEntity>
addEntityContextIds()
Union-merge new entity ids into entity.contextIds. The canonical way to
mutate the top-level multi-entity binding after creation.
Semantics:
- Visibility-validate every addition (each must be visible to the caller;
invisible ids throw — mirrors
addFact's context-visibility rule). - Filter self-references silently (id === entityId).
- Dedupe against existing
contextIds. - No-op fast path when every addition is already present.
Return shape: { entity, added } — added is the count of new
contextIds actually persisted (post dedupe + self-reference filter).
added === 0 means the call was a no-op (everything was already there or
filtered out); the returned entity is unchanged from storage. Migration /
audit callers branch on added > 0 to count real mutations.
Concurrency: read-modify-write with optimistic-version retry. Two callers
unioning different additions onto the same entity converge because the
losing-retry observes the winner's write and computes a fresh union.
maxAttempts caps at 3 — the last attempt's
OptimisticConcurrencyError bubbles on exhaustion (caller's choice to
surface or further-retry at a higher level).
async addEntityContextIds(
entityId: EntityId,
additions: EntityId[],
scope: ScopeFilter,
opts?: { maxAttempts?: number },
): Promise<{ entity: IEntity; added: number }>
Parameters:
entityId:stringadditions:string[]scope:ScopeFilteropts:{ maxAttempts?: number | undefined; } | undefined(optional)
Returns: Promise<{ entity: IEntity; added: number; }>
transitionTaskState()
Transition a task entity to a new state — the canonical way to mutate
task.metadata.state after creation.
Side effects (read-modify-write at the MemorySystem layer; adapters with native transactions may promote to atomic):
- Sets
metadata.state = newState. - Appends
metadata.stateHistory: TaskStateHistoryEntry[], keeping only the most-recentstateHistoryCapentries (default 200). Older entries drop in FIFO order.stateHistoryis the only audit trail — no separate audit fact is written. - When
newStateis intaskStates.terminalANDmetadata.completedAtis unset, setsmetadata.completedAt = at.
Validate modes:
'warn'(default): any transition allowed; out-of-matrix transitions log toconsole.warnand still proceed.'strict': out-of-matrix transitions throwInvalidTaskTransitionErrorand the metadata write is skipped.'none': silent.
Subject must be a type: 'task' entity. For non-task subjects, update
metadata directly via updateEntityMetadata.
async transitionTaskState(
taskId: EntityId,
newState: string,
opts: TransitionTaskStateOptions,
scope: ScopeFilter,
): Promise<TransitionTaskStateResult>
Parameters:
taskId:stringnewState:stringopts:TransitionTaskStateOptionsscope:ScopeFilter
Returns: Promise<TransitionTaskStateResult>
listOpenTasks()
List open (non-terminal) tasks for a scope, optionally filtered by
assignee or project. Thin wrapper around listEntities — uses the
configured taskStates.active as the $in filter on metadata.state.
Client-side sort: metadata.dueAt ascending (undefined last), then
updatedAt descending. TODO: push down once EntityListFilter.orderBy
lands on adapters.
Hard cap 200; default limit 50. Pass a smaller limit explicitly to constrain prompt-token budget when injecting into an extraction prompt.
async listOpenTasks(
scope: ScopeFilter,
opts: {
assigneeId?: EntityId;
projectId?: EntityId;
/**
* Narrow to tasks whose top-level `contextIds` array includes this
* anchor (project, deal, meeting, etc.). First-class filter — does NOT
* go through `metadataFilter` (which would query the wrong path).
*/
contextId?: EntityId;
limit?: number;
} = {},
): Promise<IEntity[]>
Parameters:
scope:ScopeFilteropts:{ assigneeId?: string | undefined; projectId?: string | undefined; contextId?: string | undefined; limit?: number | undefined; }(optional) (default:{})
Returns: Promise<IEntity[]>
listRecentTopics()
List recent topic entities for a scope. Fetches up to limit * 4 (capped
at 200) topics and filters client-side by updatedAt >= cutoff where
cutoff defaults to now - days days. Sorted by updatedAt descending.
TODO: push down once EntityListFilter.updatedAfter + orderBy land on
adapters.
async listRecentTopics(
scope: ScopeFilter,
opts: { days?: number; limit?: number } = {},
): Promise<IEntity[]>
Parameters:
scope:ScopeFilteropts:{ days?: number | undefined; limit?: number | undefined; }(optional) (default:{})
Returns: Promise<IEntity[]>
iterateOpenTasks()
Async iterator over all open tasks visible to scope, yielded in batches.
Sort: metadata.dueAt asc nulls-last, then updatedAt desc, then id
asc tiebreak. Same as listOpenTasks, but iterates the full result set
rather than truncating at 200.
Use this when you need EVERY open task (status refresh, audit sweeps,
backfills). Use listOpenTasks when you need a small bounded slice
(prompt injection).
async *iterateOpenTasks(
scope: ScopeFilter,
opts: {
assigneeId?: EntityId;
projectId?: EntityId;
/** Narrow to tasks whose top-level `contextIds` includes this anchor. */
contextId?: EntityId;
batchSize?: number;
/** Resume from a cursor returned by a prior interrupted iteration. */
startAfter?: string;
} = {},
): AsyncIterable<IEntity[]>
Parameters:
scope:ScopeFilteropts:{ assigneeId?: string | undefined; projectId?: string | undefined; contextId?: string | undefined; batchSize?: number | undefined; startAfter?: string | undefined; }(optional) (default:{})
Returns: AsyncIterable<IEntity[]>
iterateRecentTopics()
Async iterator over recent topic entities visible to scope, yielded in
batches. Sort: updatedAt desc. The days window is applied client-side
(consistent with listRecentTopics); iteration stops when either the
store is exhausted OR the first batch with all entries older than the
cutoff is encountered (since sort is desc, no younger ones exist past
that point).
async *iterateRecentTopics(
scope: ScopeFilter,
opts: { days?: number; batchSize?: number; startAfter?: string } = {},
): AsyncIterable<IEntity[]>
Parameters:
scope:ScopeFilteropts:{ days?: number | undefined; batchSize?: number | undefined; startAfter?: string | undefined; }(optional) (default:{})
Returns: AsyncIterable<IEntity[]>
iterateEntitiesByFilter()
Generic async iterator over entities matching filter. The caller MUST
pass orderBy to guarantee deterministic iteration — without it, the
adapter's natural order kicks in and the iteration result is non-portable
across stores. This method does not warn (the adapter does) but does
require the caller to be explicit.
async *iterateEntitiesByFilter(
filter: EntityListFilter,
scope: ScopeFilter,
opts: {
batchSize?: number;
startAfter?: string;
orderBy: EntityOrderBy | EntityOrderBy[];
},
): AsyncIterable<IEntity[]>
Parameters:
filter:EntityListFilterscope:ScopeFilteropts:{ batchSize?: number | undefined; startAfter?: string | undefined; orderBy: EntityOrderBy | EntityOrderBy[]; }
Returns: AsyncIterable<IEntity[]>
iterateFacts()
Generic async iterator over facts matching query. Like
iterateEntitiesByFilter, the caller MUST pass orderBy (FactOrderBy
accepts observedAt | createdAt | confidence) to ensure deterministic
iteration across stores.
async *iterateFacts(
query: FactFilter,
scope: ScopeFilter,
opts: {
batchSize?: number;
startAfter?: string;
orderBy: FactOrderBy;
},
): AsyncIterable<IFact[]>
Parameters:
query:FactFilterscope:ScopeFilteropts:{ batchSize?: number | undefined; startAfter?: string | undefined; orderBy: FactOrderBy; }
Returns: AsyncIterable<IFact[]>
getProfile()
Resolve the most-specific profile visible to the caller. Precedence: ownerId match > groupId match > global. First hit wins.
async getProfile(entityId: EntityId, scope: ScopeFilter): Promise<IFact | null>
Parameters:
entityId:stringscope:ScopeFilter
Returns: Promise<IFact | null>
traverse()
async traverse(
entityId: EntityId,
opts: TraversalOptions,
scope: ScopeFilter,
): Promise<Neighborhood>
Parameters:
entityId:stringopts:TraversalOptionsscope:ScopeFilter
Returns: Promise<Neighborhood>
embedQuery()
Embed a query string once so the resulting vector can be reused across
multiple semanticSearch / findSimilarOpenTasks calls with different
filters. Avoids re-embedding the same query in fan-out workloads
(multi-predicate searches, RAG with per-tenant filters, etc.).
Throws SemanticSearchUnavailableError when no embedder is configured —
mirroring semanticSearch's error contract so callers can rely on
embedQuery succeeding wherever semanticSearch(string, ...) would.
async embedQuery(query: string): Promise<number[]>
Parameters:
query:string
Returns: Promise<number[]>
semanticSearch()
Semantic kNN over facts. Accepts either:
string— embeds internally (one round-trip per call), ORnumber[]— a pre-computed vector fromembedQuery, reused across fan-out calls so the embed cost is paid once.
The number[] form throws only on the store-capability check; no
embedder is required (the vector is already available).
async semanticSearch(
queryOrVector: string | number[],
filter: FactFilter,
scope: ScopeFilter,
topK: number = DEFAULT_SEMANTIC_TOP_K,
): Promise<Array<{ fact: IFact; score: number }>>
Parameters:
queryOrVector:string | number[]filter:FactFilterscope:ScopeFiltertopK:number(optional) (default:DEFAULT_SEMANTIC_TOP_K)
Returns: Promise<{ fact: IFact; score: number; }[]>
regenerateProfile()
async regenerateProfile(
entityId: EntityId,
targetScope: ScopeFields,
_trigger: 'threshold' | 'manual' = 'manual',
): Promise<IFact>
Parameters:
entityId:stringtargetScope:ScopeFields_trigger:"manual" | "threshold"(optional) (default:'manual')
Returns: Promise<IFact>
deriveFactsFor()
async deriveFactsFor(entityId: EntityId, scope: ScopeFilter): Promise<IFact[]>
Parameters:
entityId:stringscope:ScopeFilter
Returns: Promise<IFact[]>
createDocument()
Create a new document. When slug is provided, the document carries
{kind:'canonical', value:'doc:<slug>'} so subsequent lookups via
getDocument(slug) resolve deterministically. When attachTo is set, an
idempotent has_document fact is written linking the parent to the new
document.
Returns the created entity. Content-embedding refresh is queued
asynchronously — read pendingEmbeddings() / flushEmbeddings() to
observe.
async createDocument(input: CreateDocumentInput, scope: ScopeFilter): Promise<Document>
Parameters:
input:CreateDocumentInputscope:ScopeFilter
Returns: Promise<Document>
updateDocument()
Update a document — body, role, format, summary, or extra metadata. All
fields optional; only provided keys are touched (shallow overwrite over
metadata). Queues a content-embedding refresh if body changed
(gated by metadataDeepEqual short-circuit in applyMetadataMerge).
idOrSlug accepts either a raw entity id or a bare slug (the doc:
prefix is added automatically when the value isn't recognized as an id).
async updateDocument(
idOrSlug: string,
patch: UpdateDocumentInput,
scope: ScopeFilter,
): Promise<Document>
Parameters:
idOrSlug:stringpatch:UpdateDocumentInputscope:ScopeFilter
Returns: Promise<Document>
getDocument()
Fetch a document by id or slug. Returns the entity verbatim (including
metadata.body). Returns null when not found / not visible.
async getDocument(idOrSlug: string, scope: ScopeFilter): Promise<Document | null>
Parameters:
idOrSlug:stringscope:ScopeFilter
Returns: Promise<Document | null>
attachDocument()
Attach a document to a parent entity via the canonical has_document
predicate. Idempotent — a no-op when an identical non-archived fact
already exists in the caller's scope.
async attachDocument(parentId: EntityId, docIdOrSlug: string, scope: ScopeFilter): Promise<void>
Parameters:
parentId:stringdocIdOrSlug:stringscope:ScopeFilter
Returns: Promise<void>
detachDocument()
Remove a has_document attachment between a parent entity and a document.
Archives EVERY matching non-archived fact in scope (idempotent), so
duplicate attachments — should they exist due to race conditions — are
cleaned up in one call.
Returns {archived, skippedDueToPermissions} so callers can distinguish
"no matching facts" (both zero) from "had matches but couldn't write any"
(archived=0, skipped>0).
async detachDocument(
parentId: EntityId,
docIdOrSlug: string,
scope: ScopeFilter,
): Promise<DetachDocumentResult>
Parameters:
parentId:stringdocIdOrSlug:stringscope:ScopeFilter
Returns: Promise<DetachDocumentResult>
listDocuments()
List documents — optionally narrowed by attachedTo, role, ownerId.
By default the response strips metadata.body (listing pages can be
large); pass includeBody: true to keep it.
async listDocuments(filter: ListDocumentsFilter, scope: ScopeFilter): Promise<Document[]>
Parameters:
filter:ListDocumentsFilterscope:ScopeFilter
Returns: Promise<Document[]>
searchDocuments()
Search documents — semantic mode uses contentEmbedding (requires an
embedder); keyword mode runs a case-insensitive substring match over
metadata.body and displayName against a page of candidates. Both
modes honor attachedTo / role filters and clamp limit to [1, 50].
Score semantics: semantic returns 0..1 cosine similarity from the
underlying semanticSearchEntities; keyword returns a coarse 0..1 hit
score (1.0 for displayName match, 0.5 for body-only).
async searchDocuments(input: SearchDocumentsInput, scope: ScopeFilter): Promise<DocumentSearchHit[]>
Parameters:
input:SearchDocumentsInputscope:ScopeFilter
Returns: Promise<DocumentSearchHit[]>
flushEmbeddings()
flushEmbeddings(): Promise<void>
Returns: Promise<void>
pendingEmbeddings()
pendingEmbeddings(): number
Returns: number
backfillContentEmbeddings()
Backfill contentEmbedding / contentEmbeddingText for entities whose
stored embedding doesn't match what the current composer would produce.
Run-once migration helper. Use cases:
- Upgrading from a release where content embedding was document-only —
backfill makes every
task/event/person/ etc. searchable semantically without waiting for each entity's next mutation. - After overriding a composer in
MemorySystemConfig.entityContentComposersso the new composer's output applies to historical data immediately. - After running
mergeEntities/archiveEntitycampaigns where cascading content refresh would be desirable.
Idempotent. Skips entities whose contentEmbeddingText already
matches the composer output (no embed cost). Safe to re-run.
Resumable. No transactional guarantees. Interrupting mid-run leaves a partially-converged population; re-running converges the remainder.
Scoping. Bounded by scope (same visibility rules as every other
read). Multi-tenant hosts iterate over scopes and call once per scope.
Concurrency. Each enqueue goes through the standard EmbeddingQueue
with bounded concurrency (config.embeddingQueue.concurrency). Caller
should await memorySystem.flushEmbeddings() after the helper returns to
wait for all in-flight embeds.
async backfillContentEmbeddings(
scope: ScopeFilter,
opts?: {
/** Narrow to specific entity types. Empty/omitted = all types. */
types?: string[];
/** Page size for the streaming scan. Default 500, max 1000. */
batchSize?: number;
/** Progress callback invoked after each page. */
onProgress?: (scanned: number, queued: number) => void;
},
): Promise<{ scanned: number; queued: number; skipped: number }>
Parameters:
scope:ScopeFilteropts:{ types?: string[] | undefined; batchSize?: number | undefined; onProgress?: ((scanned: number, queued: number) => void) | undefined; } | undefined(optional)
Returns: Promise<{ scanned: number; queued: number; skipped: number; }>
backfillFactEmbeddings()
Backfill embedding / summaryForEmbedding for facts. Sister method to
backfillContentEmbeddings — same idempotency, same scoping rules. Run
once when upgrading to a release that removes the 80-char threshold gate,
so short atomic facts (e.g. (Sarah, works_at, Acme)) become searchable
semantically without waiting for each fact's next mutation.
async backfillFactEmbeddings(
scope: ScopeFilter,
opts?: {
/** Narrow to specific fact predicates. Empty/omitted = all predicates. */
predicates?: string[];
/** Narrow to fact kind. Empty/omitted = both atomic + document. */
kind?: IFact['kind'];
/** Page size for the streaming scan. Default 500, max 1000. */
batchSize?: number;
/** Progress callback invoked after each page. */
onProgress?: (scanned: number, queued: number) => void;
},
): Promise<{ scanned: number; queued: number; skipped: number }>
Parameters:
scope:ScopeFilteropts:{ predicates?: string[] | undefined; kind?: FactKind | undefined; batchSize?: number | undefined; onProgress?: ((scanned: number, queued: number) => void) | undefined; } | undefined(optional)
Returns: Promise<{ scanned: number; queued: number; skipped: number; }>
destroy()
destroy(): void
Returns: void
shutdown()
async shutdown(): Promise<void>
Returns: Promise<void>
MemoryWritePluginNextGen class
📍 src/core/context-nextgen/plugins/MemoryWritePluginNextGen.ts:258
Constructor
constructor
constructor(config: MemoryWritePluginConfig)
Parameters:
config:MemoryWritePluginConfig
Methods
getInstructions()
getInstructions(): string | null
Returns: string | null
getContent()
async getContent(): Promise<string | null>
Returns: Promise<string | null>
getContents()
getContents(): unknown
Returns: unknown
getTokenSize()
getTokenSize(): number
Returns: number
getInstructionsTokenSize()
getInstructionsTokenSize(): number
Returns: number
isCompactable()
isCompactable(): boolean
Returns: boolean
compact()
async compact(_targetTokensToFree: number): Promise<number>
Parameters:
_targetTokensToFree:number
Returns: Promise<number>
getTools()
getTools(): ToolFunction[]
Returns: ToolFunction<any, any>[]
destroy()
destroy(): void
Returns: void
getState()
getState(): unknown
Returns: unknown
restoreState()
restoreState(_state: unknown): void
Parameters:
_state:unknown
Returns: void
Properties
| Property | Type | Description |
|---|---|---|
name |
name: "memory_write" |
- |
ModelBudgetExceededError class
📍 src/core/model-execution/ModelTask.ts:9
Constructor
constructor
constructor(public readonly limit: string)
Parameters:
limit:string
ModelTask class
📍 src/core/model-execution/ModelTask.ts:123
In-process shared admission, reservations and usage. Never stores prompts or credentials.
Constructor
constructor
constructor(options: { id?: string; limits?: ModelTaskLimits; signal?: AbortSignal } = {})
Parameters:
options:{ id?: string | undefined; limits?: ModelTaskLimits | undefined; signal?: AbortSignal | undefined; }(optional) (default:{})
Methods
cancel()
cancel(reason: unknown = new DOMException('Model task cancelled', 'AbortError')): void
Parameters:
reason:unknown(optional) (default:new DOMException('Model task cancelled', 'AbortError'))
Returns: void
execute()
Execute one logical call; installed SDK adapters admit every physical retry separately.
async execute<T>(descriptor: ModelCallDescriptor, operation: (signal: AbortSignal) => Promise<T>, options: {
signal?: AbortSignal; transport?: boolean; usage?: (result: T) => TokenUsage | undefined;
/** @internal Latest terminal usage survives an iterator failure or cancellation. */
observedUsage?: () => TokenUsage | undefined;
/** @internal Final synchronous native request/authority guard, including retries. */
beforeDispatch?: () => void;
/** @internal Observed provider/transport dispatch after successful reservation. */
onDispatch?: () => void;
} = {}): Promise<{ result: T; invocationId: string }>
Parameters:
descriptor:ModelCallDescriptoroperation:(signal: AbortSignal) => Promise<T>options:{ signal?: AbortSignal | undefined; transport?: boolean | undefined; usage?: ((result: T) => TokenUsage | undefined) | undefined; observedUsage?: (() => TokenUsage | undefined) | undefined; beforeDispatch?: (() => void) | undefined; onDispatch?: (() => void) | undefined; }(optional) (default:{})
Returns: Promise<{ result: T; invocationId: string; }>
iterate()
Streaming variant with pull-based backpressure and invocation-local transport tracking.
async *iterate<T>(descriptor: ModelCallDescriptor, factory: (signal: AbortSignal) => AsyncIterable<T>, options: {
signal?: AbortSignal; transport?: boolean; usage?: (event: T) => TokenUsage | undefined;
beforeDispatch?: () => void;
/** @internal Observed provider/transport dispatch after successful reservation. */
onDispatch?: () => void;
} = {}): AsyncIterableIterator<T>
Parameters:
descriptor:ModelCallDescriptorfactory:(signal: AbortSignal) => AsyncIterable<T>options:{ signal?: AbortSignal | undefined; transport?: boolean | undefined; usage?: ((event: T) => TokenUsage | undefined) | undefined; beforeDispatch?: (() => void) | undefined; onDispatch?: (() => void) | undefined; }(optional) (default:{})
Returns: AsyncIterableIterator<T>
recordExternal()
Host-reported usage is idempotent by ID. Conflicting replays fail explicitly.
recordExternal(report: { id: string; purpose: string; connector: string; model: string; parentInvocationId?: string; usage: TokenUsage | null;
status?: 'succeeded' | 'failed' | 'cancelled'; }): void
Parameters:
report:{ id: string; purpose: string; connector: string; model: string; parentInvocationId?: string | undefined; usage: TokenUsage | null; status?: "failed" | "cancelled" | "succeeded" | undefined; }
Returns: void
recordValidation()
Attach a host validation result without recounting the model usage.
recordValidation(invocationId: string, valid: boolean): void
Parameters:
invocationId:stringvalid:boolean
Returns: void
snapshot()
snapshot(): ModelTaskSnapshot
Returns: ModelTaskSnapshot
destroy()
destroy(): void
Returns: void
Properties
| Property | Type | Description |
|---|---|---|
id |
id: string |
- |
limits |
limits: Readonly<ModelTaskLimits> |
- |
MongoMemoryAdapter class
📍 src/memory/adapters/mongo/MongoMemoryAdapter.ts:214
Constructor
constructor
constructor(opts: MongoMemoryAdapterOptions)
Parameters:
opts:MongoMemoryAdapterOptions
Methods
ensureIndexes()
H7: ensure the recommended indexes exist. Idempotent — Mongo's
createIndex is a no-op when the index is already present with matching
specification. Callers integrate this into their migration system; the
adapter does NOT call it automatically (indexes are the client app's
responsibility, not a library concern).
Invokes the shared ensureIndexes(...) function against this adapter's
collections. See indexes.ts for the index list and why each exists.
async ensureIndexes(): Promise<void>
Returns: Promise<void>
createEntity()
async createEntity(input: NewEntity): Promise<IEntity>
Parameters:
input:NewEntity
Returns: Promise<IEntity>
createEntities()
async createEntities(inputs: NewEntity[]): Promise<IEntity[]>
Parameters:
inputs:NewEntity[]
Returns: Promise<IEntity[]>
updateEntity()
async updateEntity(entity: IEntity): Promise<void>
Parameters:
entity:IEntity
Returns: Promise<void>
getEntity()
async getEntity(id: EntityId, scope: ScopeFilter): Promise<IEntity | null>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IEntity | null>
getEntities()
async getEntities(ids: EntityId[], scope: ScopeFilter): Promise<Array<IEntity | null>>
Parameters:
ids:string[]scope:ScopeFilter
Returns: Promise<(IEntity | null)[]>
findEntitiesByIdentifier()
async findEntitiesByIdentifier(
kind: string,
value: string,
scope: ScopeFilter,
): Promise<IEntity[]>
Parameters:
kind:stringvalue:stringscope:ScopeFilter
Returns: Promise<IEntity[]>
atomicCreateOrFindByNormalizedName()
Atomic find-or-create by (type, normalizedDisplayName, scope). See the
interface contract.
Implementation: optimistic find-then-insert with duplicate-key recovery.
Cross-process atomicity depends on the unique partial index on
{groupId, ownerId, type, normalizedDisplayName} being installed —
without it, a racing insert succeeds and produces duplicates. The library
exports ensureNormalizedNameUniqueIndex for hosts to install in their
migration (not auto-installed because adding a unique index to a
collection containing duplicates fails).
async atomicCreateOrFindByNormalizedName(
input: NewEntity,
scope: ScopeFilter,
): Promise<{ entity: IEntity; created: boolean }>
Parameters:
input:NewEntityscope:ScopeFilter
Returns: Promise<{ entity: IEntity; created: boolean; }>
findEntitiesByNormalizedName()
async findEntitiesByNormalizedName(
type: string | undefined,
normalized: string,
scope: ScopeFilter,
opts?: { matchAliases?: boolean; limit?: number },
): Promise<IEntity[]>
Parameters:
type:string | undefinednormalized:stringscope:ScopeFilteropts:{ matchAliases?: boolean | undefined; limit?: number | undefined; } | undefined(optional)
Returns: Promise<IEntity[]>
searchEntities()
async searchEntities(
query: string,
opts: EntitySearchOptions,
scope: ScopeFilter,
): Promise<Page<IEntity>>
Parameters:
query:stringopts:EntitySearchOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
listEntities()
async listEntities(
filter: EntityListFilter,
opts: ListOptions,
scope: ScopeFilter,
): Promise<Page<IEntity>>
Parameters:
filter:EntityListFilteropts:ListOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
archiveEntity()
async archiveEntity(id: EntityId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
deleteEntity()
async deleteEntity(id: EntityId, scope: ScopeFilter): Promise<void>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
createFact()
async createFact(input: NewFact): Promise<IFact>
Parameters:
input:NewFact
Returns: Promise<IFact>
createFacts()
async createFacts(inputs: NewFact[]): Promise<IFact[]>
Parameters:
inputs:NewFact[]
Returns: Promise<IFact[]>
getFact()
async getFact(id: FactId, scope: ScopeFilter): Promise<IFact | null>
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IFact | null>
findFacts()
async findFacts(
filter: FactFilter,
opts: FactQueryOptions,
scope: ScopeFilter,
): Promise<Page<IFact>>
Parameters:
filter:FactFilteropts:FactQueryOptionsscope:ScopeFilter
Returns: Promise<Page<IFact>>
updateFact()
async updateFact(id: FactId, patch: Partial<IFact>, scope: ScopeFilter): Promise<void>
Parameters:
id:stringpatch:Partial<IFact>scope:ScopeFilter
Returns: Promise<void>
rewriteEntityPrincipal()
Rewrite entity:<from> → entity:<to> access grants across entities,
facts, and (if present) the facts archive. Bulk identity fix used by
mergeEntities; scoped to the caller's tenant for safety, no per-record
write check, no version bump (these are access-token rewrites, not
semantic mutations). See IMemoryStore.rewriteEntityPrincipal.
async rewriteEntityPrincipal(
fromEntityId: EntityId,
toEntityId: EntityId,
scope: ScopeFilter,
): Promise<void>
Parameters:
fromEntityId:stringtoEntityId:stringscope:ScopeFilter
Returns: Promise<void>
countFacts()
async countFacts(filter: FactFilter, scope: ScopeFilter): Promise<number>
Parameters:
filter:FactFilterscope:ScopeFilter
Returns: Promise<number>
traverse()
async traverse(
startId: EntityId,
opts: TraversalOptions,
scope: ScopeFilter,
): Promise<Neighborhood>
Parameters:
startId:stringopts:TraversalOptionsscope:ScopeFilter
Returns: Promise<Neighborhood>
semanticSearch()
async semanticSearch(
queryVector: number[],
filter: FactFilter,
opts: SemanticSearchOptions,
scope: ScopeFilter,
): Promise<Array<{ fact: IFact; score: number }>>
Parameters:
queryVector:number[]filter:FactFilteropts:SemanticSearchOptionsscope:ScopeFilter
Returns: Promise<{ fact: IFact; score: number; }[]>
semanticSearchEntities()
async semanticSearchEntities(
queryVector: number[],
filter: EntitySemanticSearchFilter,
opts: SemanticSearchOptions & { minScore?: number; embeddingField?: EntityEmbeddingField },
scope: ScopeFilter,
): Promise<Array<{ entity: IEntity; score: number }>>
Parameters:
queryVector:number[]filter:EntitySemanticSearchFilteropts:SemanticSearchOptions & { minScore?: number | undefined; embeddingField?: EntityEmbeddingField | undefined; }scope:ScopeFilter
Returns: Promise<{ entity: IEntity; score: number; }[]>
ensureVectorSearchIndexes()
Create the Atlas Vector Search indexes for facts and/or entities if they
aren't already present. Separate from ensureIndexes() because vector
indexes need runtime parameters (dimensions, similarity) and take
longer to build — callers shouldn't pay that cost unless they use
semantic search.
Requires:
- Atlas Server v6.0.11+ and mongodb node driver v6.6+ (the driver
exposes
createSearchIndex/listSearchIndexes). - The
IMongoCollectionLikewrapper must implementcreateSearchIndex(both bundled wrappers do; custom wrappers may need updating).
Idempotent — re-running is safe: existing indexes with the configured
name are detected via listSearchIndexes and skipped. Concurrent-create
races (two migrations running at once) are absorbed: if
createSearchIndex fails but the index is present on re-check, we
treat it as "another process won" and continue.
Fire-and-forget: returns as soon as Atlas accepts the create request. The index builds asynchronously on Atlas (30–60s typical). Runs during startup migrations, so the index is ready well before real traffic. The typical first query lands minutes after the migration, not seconds — no readiness wait needed.
Index names come from the adapter's config by default. If the adapter
was constructed with vectorIndexName: 'custom_facts' (or
entityVectorIndexName: 'custom_entities'), this helper creates indexes
under those names automatically. Callers can still override via
factsIndexName / entitiesIndexName, but the default is the safe
choice — runtime queries and helper output always agree.
Filter fields are auto-declared in the index definition. Atlas
$vectorSearch silently ignores filter clauses whose paths aren't
declared as type: 'filter' in the index. We declare scope + archived
- discriminator paths for both collections so scope enforcement works
on the
$vectorSearchfast path. See the field lists inFACTS_FILTER_PATHS/ENTITIES_FILTER_PATHSbelow — manual Atlas-UI creators must match or the filter is silently ignored (scope bypass).
async ensureVectorSearchIndexes(opts: {
/** Embedding dimensionality — MUST match your embedder. Must be a positive integer. */
dimensions: number;
/** Default: 'cosine'. Match the similarity your embedder was trained for. */
similarity?: 'cosine' | 'dotProduct' | 'euclidean';
/**
* Atlas index name for facts. Default: the adapter's own `vectorIndexName`
* option, or `'facts_vector'` when that's also unset. Pass `null` to skip
* the facts index entirely.
*/
factsIndexName?: string | null;
/**
* Atlas index name for entities (identityEmbedding). Default: the
* adapter's own `entityVectorIndexName` option, or `'entities_vector'`
* when unset. Pass `null` to skip.
*/
entitiesIndexName?: string | null;
/**
* Atlas index name for entity content (contentEmbedding) — used by
* document semantic search. **Opt-in.** Default: the adapter's own
* `entityContentVectorIndexName` option, or **`null` (skip)** when that's
* also unset. Hosts that use documents should either set the adapter's
* `entityContentVectorIndexName` (so the runtime path and this helper
* agree on the name) or pass `entitiesContentIndexName` explicitly here.
*
* Default-skip is deliberate: adding a third Atlas index automatically
* on `ensureVectorSearchIndexes()` would silently bill existing
* deployments that aren't using documents.
*/
entitiesContentIndexName?: string | null;
}): Promise<void>
Parameters:
opts:{ dimensions: number; similarity?: "cosine" | "dotProduct" | "euclidean" | undefined; factsIndexName?: string | null | undefined; entitiesIndexName?: string | null | undefined; entitiesContentIndexName?: string | null | undefined; }
Returns: Promise<void>
destroy()
destroy(): void
Returns: void
shutdown()
async shutdown(): Promise<void>
Returns: Promise<void>
ParallelTasksError class
📍 src/domain/errors/AIErrors.ts:339
Error thrown when multiple tasks fail in parallel execution (fail-all mode)
Constructor
constructor
constructor(
/** Array of task failures */
public readonly failures: TaskFailure[]
)
Parameters:
failures:TaskFailure[]
Methods
getErrors()
Get all failure errors
getErrors(): Error[]
Returns: Error[]
getFailedTaskIds()
Get failed task IDs
getFailedTaskIds(): string[]
Returns: string[]
PlanningAgent class
📍 src/capabilities/taskAgent/PlanningAgent.ts:90
PlanningAgent class
Static Methods
static create()
Create a new PlanningAgent
static create(config: PlanningAgentConfig): PlanningAgent
Parameters:
config:PlanningAgentConfig
Returns: PlanningAgent
Methods
generatePlan()
Generate a plan from a goal
async generatePlan(input: {
goal: string;
context?: string;
constraints?: string[];
}): Promise<GeneratedPlan>
Parameters:
input:{ goal: string; context?: string | undefined; constraints?: string[] | undefined; }
Returns: Promise<GeneratedPlan>
refinePlan()
Validate and refine an existing plan
async refinePlan(plan: Plan, feedback: string): Promise<GeneratedPlan>
Parameters:
plan:Planfeedback:string
Returns: Promise<GeneratedPlan>
getCurrentTasks()
Get current tasks (for tool access)
getCurrentTasks(): TaskInput[]
Returns: TaskInput[]
addTask()
Add task (called by planning tools)
addTask(task: TaskInput): void
Parameters:
task:TaskInput
Returns: void
updateTask()
Update task (called by planning tools)
updateTask(name: string, updates: Partial<TaskInput>): void
Parameters:
name:stringupdates:Partial<TaskInput>
Returns: void
removeTask()
Remove task (called by planning tools)
removeTask(name: string): void
Parameters:
name:string
Returns: void
finalizePlanning()
Mark planning as complete
finalizePlanning(): void
Returns: void
TaskStatePluginNextGen class
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:129
Host-controlled semantic notebook and protected prompt view; no model mutation tools.
Constructor
constructor
constructor(config: TaskStatePluginConfig)
Parameters:
config:TaskStatePluginConfig
Methods
emptyState()
emptyState(): TaskStateSnapshot
Returns: TaskStateSnapshot
updateHost()
Host-only atomic update. The model has no tool exposing this method.
updateHost(delta: TaskStateHostDelta, expectedRevision: number): TaskStateSnapshot
Parameters:
delta:TaskStateHostDeltaexpectedRevision:number
Returns: TaskStateSnapshot
propose()
Host submits model output as unconfirmed data, with source and uncertainty.
propose(notes: TaskStateNoteInput[], expectedRevision: number): TaskStateSnapshot
Parameters:
notes:TaskStateNoteInput[]expectedRevision:number
Returns: TaskStateSnapshot
assume()
assume(notes: TaskStateNoteInput[], expectedRevision: number): TaskStateSnapshot
Parameters:
notes:TaskStateNoteInput[]expectedRevision:number
Returns: TaskStateSnapshot
recordAction()
Host supplies an actual ToolManager outcome; observation recording never reexecutes an action.
recordAction(observed: ToolExecutionOutcome, expectedRevision: number): TaskStateSnapshot
Parameters:
observed:ToolExecutionOutcomeexpectedRevision:number
Returns: TaskStateSnapshot
confirmAction()
Explicit host-defined completion condition; model text/tool success cannot invoke it.
confirmAction(executionId: string, status: TaskStatus, proof: TaskStateSource, expectedRevision: number): TaskStateSnapshot
Parameters:
executionId:stringstatus:TaskStatusproof:TaskStateSourceexpectedRevision:number
Returns: TaskStateSnapshot
syncLifecycle()
Snapshot an existing Task/routine authority; this view does not drive its lifecycle.
syncLifecycle(view: TaskStateLifecycleView, expectedRevision: number): TaskStateSnapshot
Parameters:
view:TaskStateLifecycleViewexpectedRevision:number
Returns: TaskStateSnapshot
refreshMemoryTask()
Read linked task metadata through existing MemorySystem authorization. Transition it through MemorySystem directly.
async refreshMemoryTask(memory: Pick<MemorySystem, 'getEntity'>, scope: ScopeFilter, expectedRevision: number, signal?: AbortSignal): Promise<TaskStateSnapshot>
Parameters:
memory:Pick<MemorySystem, "getEntity">scope:ScopeFilterexpectedRevision:numbersignal:AbortSignal | undefined(optional)
Returns: Promise<TaskStateSnapshot>
pruneResolved()
Explicit host pruning only; unresolved constraints and unconfirmed actions cannot be evicted.
pruneResolved(noteIds: string[], executionIds: string[], expectedRevision: number): TaskStateSnapshot
Parameters:
noteIds:string[]executionIds:string[]expectedRevision:number
Returns: TaskStateSnapshot
getInstructions()
getInstructions(): string
Returns: string
getContent()
async getContent(): Promise<string>
Returns: Promise<string>
getContents()
getContents(): TaskStateSnapshot
Returns: TaskStateSnapshot
getState()
getState(): TaskStateSnapshot
Returns: TaskStateSnapshot
validateState()
Pure bounded preflight; future versions/foreign task scopes are rejected before context mutation.
validateState(value: unknown): TaskStateSnapshot
Parameters:
value:unknown
Returns: TaskStateSnapshot
restoreState()
restoreState(value: unknown): void
Parameters:
value:unknown
Returns: void
destroy()
destroy(): void
Returns: void
Properties
| Property | Type | Description |
|---|---|---|
name |
name: "task_state" |
- |
scope |
scope: Readonly<EvidenceScope> |
- |
taskId |
taskId: string |
- |
TaskTimeoutError class
📍 src/domain/errors/AIErrors.ts:292
Error thrown when a task execution times out
Constructor
constructor
constructor(
public readonly taskId: string,
public readonly taskName: string,
public readonly timeoutMs: number
)
Parameters:
taskId:stringtaskName:stringtimeoutMs:number
TaskValidationError class
📍 src/domain/errors/AIErrors.ts:311
Error thrown when task completion validation fails
Constructor
constructor
constructor(
public readonly taskId: string,
public readonly taskName: string,
public readonly reason: string
)
Parameters:
taskId:stringtaskName:stringreason:string
WorkingMemory class
📍 src/capabilities/taskAgent/WorkingMemory.ts:103
WorkingMemory manages the agent's indexed working memory.
Features:
- Store/retrieve with descriptions for index
- Scoped memory (simple or task-aware)
- Priority-based eviction (respects pinned, priority, then LRU)
- Pluggable priority calculation via PriorityCalculator strategy
- Task completion detection and stale entry notification
- Event emission for monitoring
Constructor
constructor
Create a WorkingMemory instance
constructor(
storage: IMemoryStorage,
config: WorkingMemoryConfig = DEFAULT_MEMORY_CONFIG,
priorityCalculator: PriorityCalculator = staticPriorityCalculator
)
Parameters:
storage:IMemoryStorageconfig:WorkingMemoryConfig(optional) (default:DEFAULT_MEMORY_CONFIG)priorityCalculator:PriorityCalculator(optional) (default:staticPriorityCalculator)
Methods
setPriorityCalculator()
Set the priority calculator (for switching strategies at runtime)
setPriorityCalculator(calculator: PriorityCalculator): void
Parameters:
calculator:PriorityCalculator
Returns: void
setPriorityContext()
Update priority context (e.g., task states for TaskAgent)
setPriorityContext(context: PriorityContext): void
Parameters:
context:PriorityContext
Returns: void
getPriorityContext()
Get the current priority context
getPriorityContext(): PriorityContext
Returns: PriorityContext
store()
Store a value in working memory
async store(
key: string,
description: string,
value: unknown,
options?: {
scope?: MemoryScope;
priority?: MemoryPriority;
pinned?: boolean;
}
): Promise<void>
Parameters:
key:stringdescription:stringvalue:unknownoptions:{ scope?: MemoryScope | undefined; priority?: MemoryPriority | undefined; pinned?: boolean | undefined; } | undefined(optional)
Returns: Promise<void>
getMaxIndexEntries()
Get the configured max index entries limit
getMaxIndexEntries(): number | undefined
Returns: number | undefined
storeForTasks()
Store a value scoped to specific tasks Convenience method for task-aware memory
async storeForTasks(
key: string,
description: string,
value: unknown,
taskIds: string[],
options?: { priority?: MemoryPriority; pinned?: boolean }
): Promise<void>
Parameters:
key:stringdescription:stringvalue:unknowntaskIds:string[]options:{ priority?: MemoryPriority | undefined; pinned?: boolean | undefined; } | undefined(optional)
Returns: Promise<void>
storeForPlan()
Store a value scoped to the entire plan Convenience method for plan-scoped memory
async storeForPlan(
key: string,
description: string,
value: unknown,
options?: { priority?: MemoryPriority; pinned?: boolean }
): Promise<void>
Parameters:
key:stringdescription:stringvalue:unknownoptions:{ priority?: MemoryPriority | undefined; pinned?: boolean | undefined; } | undefined(optional)
Returns: Promise<void>
retrieve()
Retrieve a value from working memory
Note: Access stats update is not strictly atomic. Under very high concurrency, accessCount may be slightly inaccurate. This is acceptable for memory management purposes where exact counts are not critical.
async retrieve(key: string): Promise<unknown>
Parameters:
key:string
Returns: Promise<unknown>
retrieveMany()
Retrieve multiple values
async retrieveMany(keys: string[]): Promise<Record<string, unknown>>
Parameters:
keys:string[]
Returns: Promise<Record<string, unknown>>
delete()
Delete a value from working memory
async delete(key: string): Promise<void>
Parameters:
key:string
Returns: Promise<void>
has()
Check if key exists
async has(key: string): Promise<boolean>
Parameters:
key:string
Returns: Promise<boolean>
persist()
Promote an entry to persistent scope Works with both simple and task-aware scopes
async persist(key: string): Promise<void>
Parameters:
key:string
Returns: Promise<void>
pin()
Pin an entry (never evicted)
async pin(key: string): Promise<void>
Parameters:
key:string
Returns: Promise<void>
unpin()
Unpin an entry
async unpin(key: string, newPriority: MemoryPriority = 'normal'): Promise<void>
Parameters:
key:stringnewPriority:MemoryPriority(optional) (default:'normal')
Returns: Promise<void>
setPriority()
Set the base priority of an entry
async setPriority(key: string, priority: MemoryPriority): Promise<void>
Parameters:
key:stringpriority:MemoryPriority
Returns: Promise<void>
updateScope()
Update the scope of an entry without re-storing the value
async updateScope(key: string, scope: MemoryScope): Promise<void>
Parameters:
key:stringscope:MemoryScope
Returns: Promise<void>
addTasksToScope()
Add task IDs to an existing task-scoped entry If entry is not task-scoped, converts it to task-scoped
async addTasksToScope(key: string, taskIds: string[]): Promise<void>
Parameters:
key:stringtaskIds:string[]
Returns: Promise<void>
clearScope()
Clear all entries of a specific scope
async clearScope(scope: MemoryScope): Promise<void>
Parameters:
scope:MemoryScope
Returns: Promise<void>
clear()
Clear all entries
async clear(): Promise<void>
Returns: Promise<void>
getIndex()
Get memory index with computed effective priorities Respects maxIndexEntries limit for context display
async getIndex(): Promise<MemoryIndex>
Returns: Promise<MemoryIndex>
formatIndex()
Format index for context injection
async formatIndex(): Promise<string>
Returns: Promise<string>
evict()
Evict entries using specified strategy
Eviction order:
- Never evict pinned entries
- Evict low priority first, then normal, then high (never critical)
- Within same priority, use strategy (LRU or largest size)
async evict(count: number, strategy: EvictionStrategy = 'lru'): Promise<string[]>
Parameters:
count:numberstrategy:EvictionStrategy(optional) (default:'lru')
Returns: Promise<string[]>
evictLRU()
Evict entries using priority-aware LRU algorithm
async evictLRU(count: number): Promise<string[]>
Parameters:
count:number
Returns: Promise<string[]>
evictBySize()
Evict largest entries first (priority-aware)
async evictBySize(count: number): Promise<string[]>
Parameters:
count:number
Returns: Promise<string[]>
onTaskComplete()
Handle task completion - detect and notify about stale entries
Call this when a task completes to:
- Update priority context with new task state
- Detect entries that became stale
- Emit event to notify LLM about stale entries
async onTaskComplete(
taskId: string,
taskStates: Map<string, TaskStatusForMemory>
): Promise<StaleEntryInfo[]>
Parameters:
taskId:stringtaskStates:Map<string, TaskStatusForMemory>
Returns: Promise<StaleEntryInfo[]>
evictCompletedTaskEntries()
Evict entries for completed tasks
Removes entries that were scoped only to completed tasks. Use after onTaskComplete() if you want automatic cleanup.
async evictCompletedTaskEntries(
taskStates: Map<string, TaskStatusForMemory>
): Promise<string[]>
Parameters:
taskStates:Map<string, TaskStatusForMemory>
Returns: Promise<string[]>
getAccess()
Get limited memory access for tools
This provides a simplified interface for tools to interact with memory without exposing the full WorkingMemory API.
getAccess(): WorkingMemoryAccess
Returns: WorkingMemoryAccess
storeRaw()
Store raw data (low priority, first to be evicted)
Use this for original/unprocessed data that should be summarized. Raw data is automatically evicted first when memory pressure is high.
async storeRaw(
key: string,
description: string,
value: unknown,
options?: { taskIds?: string[]; scope?: MemoryScope }
): Promise<void>
Parameters:
key:stringdescription:stringvalue:unknownoptions:{ taskIds?: string[] | undefined; scope?: MemoryScope | undefined; } | undefined(optional)
Returns: Promise<void>
storeSummary()
Store a summary derived from raw data (normal priority)
Use this for processed/summarized data that extracts key information. Links back to source data for cleanup tracking.
async storeSummary(
key: string,
description: string,
value: unknown,
derivedFrom: string | string[],
options?: { taskIds?: string[]; scope?: MemoryScope }
): Promise<void>
Parameters:
key:stringdescription:stringvalue:unknownderivedFrom:string | string[]options:{ taskIds?: string[] | undefined; scope?: MemoryScope | undefined; } | undefined(optional)
Returns: Promise<void>
storeFindings()
Store final findings (high priority, kept longest)
Use this for conclusions, insights, or final results that should be preserved. These are the last to be evicted and typically span the entire plan.
async storeFindings(
key: string,
description: string,
value: unknown,
_derivedFrom?: string | string[],
options?: { taskIds?: string[]; scope?: MemoryScope; pinned?: boolean }
): Promise<void>
Parameters:
key:stringdescription:stringvalue:unknown_derivedFrom:string | string[] | undefined(optional)options:{ taskIds?: string[] | undefined; scope?: MemoryScope | undefined; pinned?: boolean | undefined; } | undefined(optional)
Returns: Promise<void>
cleanupRawData()
Clean up raw data after summary/findings are created
Call this after creating summaries to free up memory used by raw data. Only deletes entries in the 'raw' tier.
async cleanupRawData(derivedFromKeys: string[]): Promise<number>
Parameters:
derivedFromKeys:string[]
Returns: Promise<number>
getByTier()
Get all entries by tier
async getByTier(tier: MemoryTier): Promise<MemoryEntry[]>
Parameters:
tier:MemoryTier
Returns: Promise<MemoryEntry[]>
promote()
Promote an entry to a higher tier
Changes the key prefix and updates priority. Use this when raw data becomes more valuable (e.g., frequently accessed).
async promote(key: string, toTier: MemoryTier): Promise<string>
Parameters:
key:stringtoTier:MemoryTier
Returns: Promise<string>
getTierStats()
Get tier statistics
async getTierStats(): Promise<Record<MemoryTier, { count: number; sizeBytes: number }>>
Returns: Promise<Record<MemoryTier, { count: number; sizeBytes: number; }>>
getStats()
Get statistics about memory usage
async getStats(): Promise<{
totalEntries: number;
totalSizeBytes: number;
utilizationPercent: number;
byPriority: Record<MemoryPriority, number>;
pinnedCount: number;
}>
Returns: Promise<{ totalEntries: number; totalSizeBytes: number; utilizationPercent: number; byPriority: Record<MemoryPriority, number>; pinnedCount: number; }>
getLimit()
Get the configured memory limit
getLimit(): number
Returns: number
destroy()
Destroy the WorkingMemory instance Removes all event listeners and clears internal state
destroy(): void
Returns: void
serialize()
Serialize all memory entries for persistence
Returns a serializable representation of all memory entries that can be saved to storage and restored later.
async serialize(): Promise<SerializedMemory>
Returns: Promise<SerializedMemory>
restore()
Restore memory entries from serialized state
Clears existing memory and repopulates from the serialized state. Timestamps are reset to current time.
async restore(state: SerializedMemory): Promise<void>
Parameters:
state:SerializedMemory
Returns: Promise<void>
WorkingMemoryPluginNextGen class
📍 src/core/context-nextgen/plugins/WorkingMemoryPluginNextGen.ts:95
Constructor
constructor
constructor(pluginConfig: WorkingMemoryPluginConfig = {})
Parameters:
pluginConfig:WorkingMemoryPluginConfig(optional) (default:{})
Methods
getInstructions()
getInstructions(): string
Returns: string
getContent()
async getContent(): Promise<string | null>
Returns: Promise<string | null>
getContents()
getContents(): unknown
Returns: unknown
getTokenSize()
getTokenSize(): number
Returns: number
getInstructionsTokenSize()
getInstructionsTokenSize(): number
Returns: number
isCompactable()
isCompactable(): boolean
Returns: boolean
compact()
async compact(_targetTokensToFree: number): Promise<number>
Parameters:
_targetTokensToFree:number
Returns: Promise<number>
getTools()
getTools(): ToolFunction[]
Returns: ToolFunction<any, any>[]
destroy()
destroy(): void
Returns: void
getState()
getState(): SerializedWorkingMemoryState
Returns: SerializedWorkingMemoryState
restoreState()
restoreState(state: unknown): void
Parameters:
state:unknown
Returns: void
getStoreSchema()
getStoreSchema(): StoreEntrySchema
Returns: StoreEntrySchema
storeGet()
async storeGet(key?: string, _context?: ToolContext): Promise<StoreGetResult>
Parameters:
key:string | undefined(optional)_context:ToolContext | undefined(optional)
Returns: Promise<StoreGetResult>
storeSet()
async storeSet(key: string, data: Record<string, unknown>, _context?: ToolContext): Promise<StoreSetResult>
Parameters:
key:stringdata:Record<string, unknown>_context:ToolContext | undefined(optional)
Returns: Promise<StoreSetResult>
storeDelete()
async storeDelete(key: string, _context?: ToolContext): Promise<StoreDeleteResult>
Parameters:
key:string_context:ToolContext | undefined(optional)
Returns: Promise<StoreDeleteResult>
storeList()
async storeList(filter?: Record<string, unknown>, _context?: ToolContext): Promise<StoreListResult>
Parameters:
filter:Record<string, unknown> | undefined(optional)_context:ToolContext | undefined(optional)
Returns: Promise<StoreListResult>
storeAction()
async storeAction(action: string, params?: Record<string, unknown>, _context?: ToolContext): Promise<StoreActionResult>
Parameters:
action:stringparams:Record<string, unknown> | undefined(optional)_context:ToolContext | undefined(optional)
Returns: Promise<StoreActionResult>
store()
Store a value in memory
async store(
key: string,
description: string,
value: unknown,
options?: {
scope?: MemoryScope;
priority?: MemoryPriority;
tier?: MemoryTier;
pinned?: boolean;
}
): Promise<{ key: string; sizeBytes: number }>
Parameters:
key:stringdescription:stringvalue:unknownoptions:{ scope?: MemoryScope | undefined; priority?: MemoryPriority | undefined; tier?: MemoryTier | undefined; pinned?: boolean | undefined; } | undefined(optional)
Returns: Promise<{ key: string; sizeBytes: number; }>
retrieve()
Retrieve a value from memory
async retrieve(key: string): Promise<unknown | undefined>
Parameters:
key:string
Returns: Promise<unknown>
delete()
Delete a key from memory
async delete(key: string): Promise<boolean>
Parameters:
key:string
Returns: Promise<boolean>
query()
Query memory entries
async query(options?: {
pattern?: string;
tier?: MemoryTier;
includeValues?: boolean;
includeStats?: boolean;
}): Promise<{
entries: Array<{
key: string;
description: string;
tier?: MemoryTier;
value?: unknown;
}>;
stats?: { count: number; totalBytes: number };
}>
Parameters:
options:{ pattern?: string | undefined; tier?: MemoryTier | undefined; includeValues?: boolean | undefined; includeStats?: boolean | undefined; } | undefined(optional)
Returns: Promise<{ entries: { key: string; description: string; tier?: MemoryTier | undefined; value?: unknown; }[]; stats?: { count: number; totalBytes: number; } | undefined; }>
formatIndex()
Format memory index for context
async formatIndex(): Promise<string>
Returns: Promise<string>
evict()
Evict entries to free space
async evict(count: number, strategy: EvictionStrategy = 'lru'): Promise<string[]>
Parameters:
count:numberstrategy:EvictionStrategy(optional) (default:'lru')
Returns: Promise<string[]>
cleanupRaw()
Cleanup raw tier entries
async cleanupRaw(): Promise<{ deleted: number; keys: string[] }>
Returns: Promise<{ deleted: number; keys: string[]; }>
Properties
| Property | Type | Description |
|---|---|---|
name |
name: "working_memory" |
- |
CheckpointStrategy interface
📍 src/capabilities/taskAgent/CheckpointManager.ts:8
Properties
| Property | Type | Description |
|---|---|---|
afterToolCalls? |
afterToolCalls?: number; |
Checkpoint after every N tool calls |
afterLLMCalls? |
afterLLMCalls?: number; |
Checkpoint after every N LLM calls |
intervalMs? |
intervalMs?: number; |
Checkpoint on time interval |
beforeExternalWait |
beforeExternalWait: boolean; |
Always checkpoint before external wait |
mode |
`mode: 'sync' | 'async';` |
ExternalDependency interface
📍 src/domain/entities/Task.ts:71
External dependency configuration
Properties
| Property | Type | Description |
|---|---|---|
type |
`type: 'webhook' | 'poll' |
webhookId? |
webhookId?: string; |
For webhook: unique ID to match incoming webhook |
pollConfig? |
`pollConfig?: { |
toolName: string;
toolArgs: Record<string, unknown>;
intervalMs: number;
maxAttempts: number;
};| For poll: how to check if complete | |scheduledAt?|scheduledAt?: number;| For scheduled: when to resume | |manualDescription?|manualDescription?: string;| For manual: description of what's needed | |timeoutMs?|timeoutMs?: number;| Timeout for all types | |state|state: 'waiting' | 'received' | 'timeout';| Current state | |receivedData?|receivedData?: unknown;| Data received from external source | |receivedAt?|receivedAt?: number;` | - |
ExternalDependencyEvents interface
📍 src/capabilities/taskAgent/ExternalDependencyHandler.ts:10
Properties
| Property | Type | Description |
|---|---|---|
'webhook:received' |
'webhook:received': { webhookId: string; data: unknown }; |
- |
'poll:success' |
'poll:success': { taskId: string; data: unknown }; |
- |
'poll:timeout' |
'poll:timeout': { taskId: string }; |
- |
'scheduled:triggered' |
'scheduled:triggered': { taskId: string }; |
- |
'manual:completed' |
'manual:completed': { taskId: string; data: unknown }; |
- |
GeneratedPlan interface
📍 src/capabilities/taskAgent/PlanningAgent.ts:36
Generated plan with metadata
Properties
| Property | Type | Description |
|---|---|---|
plan |
plan: Plan; |
- |
reasoning |
reasoning: string; |
- |
estimated_duration? |
estimated_duration?: string; |
- |
complexity? |
`complexity?: 'low' | 'medium' |
IMemoryStorage interface
📍 src/domain/interfaces/IMemoryStorage.ts:14
Methods
get()
Get entry by key
get(key: string): Promise<MemoryEntry | undefined>;
Parameters:
key:string
Returns: Promise<MemoryEntry | undefined>
set()
Set/update entry
set(key: string, entry: MemoryEntry): Promise<void>;
Parameters:
key:stringentry:MemoryEntry
Returns: Promise<void>
delete()
Delete entry
delete(key: string): Promise<void>;
Parameters:
key:string
Returns: Promise<void>
has()
Check if key exists
has(key: string): Promise<boolean>;
Parameters:
key:string
Returns: Promise<boolean>
getAll()
Get all entries
getAll(): Promise<MemoryEntry[]>;
Returns: Promise<MemoryEntry[]>
getByScope()
Get entries by scope
getByScope(scope: MemoryScope): Promise<MemoryEntry[]>;
Parameters:
scope:MemoryScope
Returns: Promise<MemoryEntry[]>
clearScope()
Clear all entries with given scope
clearScope(scope: MemoryScope): Promise<void>;
Parameters:
scope:MemoryScope
Returns: Promise<void>
clear()
Clear everything
clear(): Promise<void>;
Returns: Promise<void>
getTotalSize()
Get total size in bytes
getTotalSize(): Promise<number>;
Returns: Promise<number>
IMemoryStore interface
Storage contract. Required methods are the minimum capability; optional
methods (traverse, semanticSearch) are discovered by duck-typing.
Id generation: adapters own id assignment. createEntity / createFact
return a fully-formed record with its id populated. Callers never pass ids
for new records.
Adapter responsibilities:
- Apply
ScopeFilterto every read — MemorySystem also filters, but the adapter must provide defence-in-depth. - Assign primary ids on create. Native mechanisms preferred (Mongo ObjectId, Meteor Random.id(), UUID for in-memory).
- Enforce optimistic concurrency on
updateEntity: reject if incomingversion !== stored.version + 1. - Hide archived records by default; return them only when an explicit
archived: truefilter is passed. - Support
asOfon fact queries (validFrom ≤ asOf ≤ validUntil ?? ∞ANDcreatedAt ≤ asOf). - When possible, expose a transactional primitive for supersession — MemorySystem currently writes the new fact before archiving the predecessor (crash-safe ordering) but adapters with native transactions may promote this to a single atomic operation.
Methods
createEntity()
Insert a new entity. Adapter assigns id + version (1) + timestamps. Returns the created record.
createEntity(input: NewEntity): Promise<IEntity>;
Parameters:
input:NewEntity
Returns: Promise<IEntity>
createEntities()
Batch insert. Returned array is in the same order as input.
createEntities(inputs: NewEntity[]): Promise<IEntity[]>;
Parameters:
inputs:NewEntity[]
Returns: Promise<IEntity[]>
updateEntity()
Update an existing entity. Incoming version must equal stored.version + 1.
updateEntity(entity: IEntity): Promise<void>;
Parameters:
entity:IEntity
Returns: Promise<void>
getEntity()
getEntity(id: EntityId, scope: ScopeFilter): Promise<IEntity | null>;
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IEntity | null>
getEntities()
Batch fetch entities by id. Returned array is in the same order as input;
ids not found or filtered out by scope resolve to null in-place. Lets
callers render references (e.g. fact.objectId → entity displayName)
without firing N round-trips. Adapters SHOULD implement this as a single
native batch query (e.g. Mongo {_id:{$in:[…]}}); the default InMemory
impl maps over getEntity.
getEntities(ids: EntityId[], scope: ScopeFilter): Promise<Array<IEntity | null>>;
Parameters:
ids:string[]scope:ScopeFilter
Returns: Promise<(IEntity | null)[]>
findEntitiesByIdentifier()
findEntitiesByIdentifier(kind: string, value: string, scope: ScopeFilter): Promise<IEntity[]>;
Parameters:
kind:stringvalue:stringscope:ScopeFilter
Returns: Promise<IEntity[]>
findEntitiesByNormalizedName()
O(1) indexed exact-match lookup for entities whose normalizedDisplayName
(or, when opts.matchAliases, normalizedAliases) equals normalized.
Used by EntityResolver Tier 2/3 in place of the legacy
searchEntities(q, {limit:50}) substring-then-filter path which was both
order-sensitive (Mongo oversample cap could truncate exact matches under
heavy substring fan-out) and non-atomic.
Contract:
- Returns at most
opts.limitentities (default 20). - Equality is byte-for-byte against the stored normalized field — callers
must normalize the query string the same way (
normalizeSurface). - Archived entities excluded.
- Scope filter applied identically to other reads.
- Entities lacking
normalizedDisplayName(legacy pre-0.8.0 data) are treated as if the field doesn't match — they are NOT returned. Hosts upgrading from <0.8.0 must runMemorySystem.backfillNormalizedFieldsto populate. - When
opts.matchAliasesis true, the method returns entities matching EITHERnormalizedDisplayName === normalizedORnormalized ∈ normalizedAliases. Default isfalse(displayName only).
findEntitiesByNormalizedName(
type: string | undefined,
normalized: string,
scope: ScopeFilter,
opts?: { matchAliases?: boolean; limit?: number },
): Promise<IEntity[]>;
Parameters:
type:string | undefinednormalized:stringscope:ScopeFilteropts:{ matchAliases?: boolean | undefined; limit?: number | undefined; } | undefined(optional)
Returns: Promise<IEntity[]>
atomicCreateOrFindByNormalizedName()
Atomically find-or-create an entity by (type, normalizedDisplayName, scope). The load-bearing primitive for eliminating concurrent-insert
duplicates — two callers racing the same surface converge on a single row.
Returns:
{ entity, created: true }— no prior row existed; the adapter insertedinputand returned the fresh entity.{ entity, created: false }— a row was already present (or another writer beat us to insert); the adapter returned the winner unchanged. Callers are responsible for accumulating aliases/identifiers/metadata onto the winner after the fact (seeMemorySystem.tryAtomicCreateOrResolve).
Atomicity guarantees:
- InMemory: enforced by the JS event-loop (no
awaitbetween read and write inside the method body) — single-process only. - Mongo: enforced by a unique partial index on
{groupId, ownerId, type, normalizedDisplayName}. Hosts must install this index viaensureNormalizedNameUniqueIndex(exported from the library) — otherwise the method degrades to a non-atomic find-then-create that still races. Adding a unique index to a collection containing duplicates fails hard, so the library refuses to auto-create it; run afterbackfillNormalizedFields+ a dedup pass.
Degraded path:
- When
normalizeSurface(input.displayName)collapses to empty (pure punctuation / whitespace), the adapter cannot index a stable key. Falls back to plaincreateEntity— guaranteedcreated: true. The caller will see a duplicate in the rare case of two concurrent writes of pure-punctuation displayNames.
atomicCreateOrFindByNormalizedName(
input: NewEntity,
scope: ScopeFilter,
): Promise<{ entity: IEntity; created: boolean }>;
Parameters:
input:NewEntityscope:ScopeFilter
Returns: Promise<{ entity: IEntity; created: boolean; }>
searchEntities()
searchEntities(query: string, opts: EntitySearchOptions, scope: ScopeFilter): Promise<Page<IEntity>>;
Parameters:
query:stringopts:EntitySearchOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
listEntities()
listEntities(filter: EntityListFilter, opts: ListOptions, scope: ScopeFilter): Promise<Page<IEntity>>;
Parameters:
filter:EntityListFilteropts:ListOptionsscope:ScopeFilter
Returns: Promise<Page<IEntity>>
archiveEntity()
archiveEntity(id: EntityId, scope: ScopeFilter): Promise<void>;
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
deleteEntity()
Hard delete — MemorySystem gates this with an explicit flag.
deleteEntity(id: EntityId, scope: ScopeFilter): Promise<void>;
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<void>
createFact()
Insert a new fact. Adapter assigns id + createdAt. Returns the created record.
createFact(input: NewFact): Promise<IFact>;
Parameters:
input:NewFact
Returns: Promise<IFact>
createFacts()
Batch insert. Returned array is in the same order as input.
createFacts(inputs: NewFact[]): Promise<IFact[]>;
Parameters:
inputs:NewFact[]
Returns: Promise<IFact[]>
getFact()
getFact(id: FactId, scope: ScopeFilter): Promise<IFact | null>;
Parameters:
id:stringscope:ScopeFilter
Returns: Promise<IFact | null>
findFacts()
findFacts(filter: FactFilter, opts: FactQueryOptions, scope: ScopeFilter): Promise<Page<IFact>>;
Parameters:
filter:FactFilteropts:FactQueryOptionsscope:ScopeFilter
Returns: Promise<Page<IFact>>
updateFact()
Patch fields on an existing fact. Used for archiving + embedding writes.
updateFact(id: FactId, patch: Partial<IFact>, scope: ScopeFilter): Promise<void>;
Parameters:
id:stringpatch:Partial<IFact>scope:ScopeFilter
Returns: Promise<void>
countFacts()
countFacts(filter: FactFilter, scope: ScopeFilter): Promise<number>;
Parameters:
filter:FactFilterscope:ScopeFilter
Returns: Promise<number>
rewriteEntityPrincipal()?
Rewrite every entity:<fromEntityId> access grant to entity:<toEntityId>
across all entities + facts (in readPrincipals, writePrincipals, and
acl). Used by mergeEntities so access grants follow identity
convergence — when a duplicate Person is merged away, records previously
readable via the loser's principal become readable via the winner's. Bulk
identity fix: scoped to the caller's tenant (scope.groupId) for safety,
but does not run per-record write checks (the merge already enforced
same-tenant + write access on winner/loser).
Optional capability — stores that don't implement it simply skip this step (their merges won't rewrite ACL grants). Both built-in adapters implement it.
rewriteEntityPrincipal?(
fromEntityId: EntityId,
toEntityId: EntityId,
scope: ScopeFilter,
): Promise<void>;
Parameters:
fromEntityId:stringtoEntityId:stringscope:ScopeFilter
Returns: Promise<void>
traverse()?
traverse?(startId: EntityId, opts: TraversalOptions, scope: ScopeFilter): Promise<Neighborhood>;
Parameters:
startId:stringopts:TraversalOptionsscope:ScopeFilter
Returns: Promise<Neighborhood>
semanticSearch()?
semanticSearch?(
queryVector: number[],
filter: FactFilter,
opts: SemanticSearchOptions,
scope: ScopeFilter,
): Promise<Array<{ fact: IFact; score: number }>>;
Parameters:
queryVector:number[]filter:FactFilteropts:SemanticSearchOptionsscope:ScopeFilter
Returns: Promise<{ fact: IFact; score: number; }[]>
semanticSearchEntities()?
Entity-level semantic search. Default field is identityEmbedding,
consumed by EntityResolver's semantic tier (Tier 4) when
EntityResolutionConfig.enableSemanticResolution is on. With
opts.embeddingField:'content', the same method searches
contentEmbedding — used by MemorySystem.searchDocuments. Adapters
that don't implement this are skipped — the resolver falls back to
Tiers 1-3.
Contract:
filter.type/filter.typesnarrow byIEntity.type. At least one should be used in practice — otherwise every entity type is a candidate.opts.topKclamped by the caller (resolver passes small values, ~10).opts.minScoreoptional noise floor (adapter MAY pre-filter; resolver post-filters regardless, so implementations can ignore it).opts.embeddingFieldselects which entity embedding to search; defaults to'identity'for backward compatibility. Adapters MUST NOT silently fall back to the other field on misses — return an empty result.- Results ordered by descending
score(cosine similarity, 0..1). - Archived entities MUST be excluded.
scopeenforcement identical to every other read.
semanticSearchEntities?(
queryVector: number[],
filter: EntitySemanticSearchFilter,
opts: SemanticSearchOptions & { minScore?: number; embeddingField?: EntityEmbeddingField },
scope: ScopeFilter,
): Promise<Array<{ entity: IEntity; score: number }>>;
Parameters:
queryVector:number[]filter:EntitySemanticSearchFilteropts:SemanticSearchOptions & { minScore?: number | undefined; embeddingField?: EntityEmbeddingField | undefined; }scope:ScopeFilter
Returns: Promise<{ entity: IEntity; score: number; }[]>
destroy()
destroy(): void;
Returns: void
shutdown()?
shutdown?(): Promise<void>;
Returns: Promise<void>
InContextMemoryConfig interface
📍 src/core/context-nextgen/plugins/InContextMemoryPluginNextGen.ts:38
Properties
| Property | Type | Description |
|---|---|---|
maxEntries? |
maxEntries?: number; |
Maximum number of entries (default: 20) |
maxTotalTokens? |
maxTotalTokens?: number; |
Maximum total tokens for all entries (default: 40000) |
defaultPriority? |
defaultPriority?: InContextPriority; |
Default priority for new entries (default: 'normal') |
showTimestamps? |
showTimestamps?: boolean; |
Whether to show timestamps in output (default: false) |
onEntriesChanged? |
onEntriesChanged?: (entries: InContextEntry[]) => void; |
Callback fired when entries change. Receives all current entries. |
InMemoryAdapterOptions interface
📍 src/memory/adapters/inmemory/InMemoryAdapter.ts:49
Properties
| Property | Type | Description |
|---|---|---|
entities? |
entities?: IEntity[]; |
Seed data for tests. |
facts? |
facts?: IFact[]; |
- |
IPlanStorage interface
📍 src/domain/interfaces/IPlanStorage.ts:8
Methods
savePlan()
Save or update a plan
savePlan(plan: Plan): Promise<void>;
Parameters:
plan:Plan
Returns: Promise<void>
getPlan()
Get plan by ID
getPlan(planId: string): Promise<Plan | undefined>;
Parameters:
planId:string
Returns: Promise<Plan | undefined>
updateTask()
Update a specific task within a plan
updateTask(planId: string, task: Task): Promise<void>;
Parameters:
planId:stringtask:Task
Returns: Promise<void>
addTask()
Add a new task to a plan (for dynamic task creation)
addTask(planId: string, task: Task): Promise<void>;
Parameters:
planId:stringtask:Task
Returns: Promise<void>
deletePlan()
Delete a plan
deletePlan(planId: string): Promise<void>;
Parameters:
planId:string
Returns: Promise<void>
listPlans()
List plans by status
listPlans(filter?: { status?: PlanStatus[] }): Promise<Plan[]>;
Parameters:
filter:{ status?: PlanStatus[] | undefined; } | undefined(optional)
Returns: Promise<Plan[]>
findByWebhookId()
Find plans with tasks waiting on a specific webhook
findByWebhookId(webhookId: string): Promise<{ plan: Plan; task: Task } | undefined>;
Parameters:
webhookId:string
Returns: Promise<{ plan: Plan; task: Task; } | undefined>
IScopedMemoryView interface
Read-only view scoped to a specific caller, passed to the rule engine. Rules CANNOT write through this view — they return partial IFact specs that MemorySystem validates and persists.
Methods
getEntity()
getEntity(id: EntityId): Promise<IEntity | null>;
Parameters:
id:string
Returns: Promise<IEntity | null>
findFacts()
findFacts(filter: FactFilter, opts?: { limit?: number }): Promise<IFact[]>;
Parameters:
filter:FactFilteropts:{ limit?: number | undefined; } | undefined(optional)
Returns: Promise<IFact[]>
MemoryConnectorsConfig interface
📍 src/memory/integration/createMemorySystemWithConnectors.ts:19
Properties
| Property | Type | Description |
|---|---|---|
embedding? |
embedding?: ConnectorEmbedderConfig; |
- |
profile? |
profile?: ConnectorProfileGeneratorConfig; |
- |
MemoryEntry interface
📍 src/domain/entities/Memory.ts:380
Single memory entry stored in working memory
Properties
| Property | Type | Description |
|---|---|---|
key |
key: string; |
- |
description |
description: string; |
- |
value |
value: unknown; |
- |
sizeBytes |
sizeBytes: number; |
- |
scope |
scope: MemoryScope; |
- |
basePriority |
basePriority: MemoryPriority; |
- |
pinned |
pinned: boolean; |
- |
createdAt |
createdAt: number; |
- |
lastAccessedAt |
lastAccessedAt: number; |
- |
accessCount |
accessCount: number; |
- |
MemoryEntryInput interface
📍 src/domain/entities/Memory.ts:444
Input for creating a memory entry
Properties
| Property | Type | Description |
|---|---|---|
key |
key: string; |
- |
description |
description: string; |
- |
value |
value: unknown; |
- |
scope? |
scope?: MemoryScope; |
Scope - defaults to 'session' for basic agents |
priority? |
priority?: MemoryPriority; |
Base priority - may be overridden by dynamic calculation |
pinned? |
pinned?: boolean; |
If true, entry is never evicted |
MemoryIndex interface
📍 src/domain/entities/Memory.ts:408
Full memory index with metadata
Properties
| Property | Type | Description |
|---|---|---|
entries |
entries: MemoryIndexEntry[]; |
- |
totalSizeBytes |
totalSizeBytes: number; |
- |
totalSizeHuman |
totalSizeHuman: string; |
- |
limitBytes |
limitBytes: number; |
- |
limitHuman |
limitHuman: string; |
- |
utilizationPercent |
utilizationPercent: number; |
- |
totalEntryCount |
totalEntryCount: number; |
Total entry count (before any truncation for display) |
omittedCount |
omittedCount: number; |
Number of entries omitted from display due to maxIndexEntries limit |
MemoryIndexEntry interface
📍 src/domain/entities/Memory.ts:396
Index entry (lightweight, always in context)
Properties
| Property | Type | Description |
|---|---|---|
key |
key: string; |
- |
description |
description: string; |
- |
size |
size: string; |
- |
scope |
scope: MemoryScope; |
- |
effectivePriority |
effectivePriority: MemoryPriority; |
- |
pinned |
pinned: boolean; |
- |
MemoryPluginConfig interface
📍 src/core/context-nextgen/plugins/MemoryPluginNextGen.ts:126
Properties
| Property | Type | Description |
|---|---|---|
memory |
memory: MemorySystem; |
Live memory system. REQUIRED. |
agentId |
agentId: string; |
Agent id — unique per agent definition. |
userId |
userId: string; |
Current user id. REQUIRED — the memory layer's owner invariant means every |
| bootstrapped entity needs an owner. Host app should pass the logged-in | ||
user's id (auto-filled from AgentContextNextGen.userId when wired via |
||
| feature flag). | ||
groupId? |
groupId?: string; |
Trusted group id for the caller (authenticated by the host app). |
| Closed into tool deps so every memory call uses this groupId. Tools do | ||
| NOT accept a groupId arg from the LLM — see the security review. Leave | ||
| undefined for non-grouped deployments. | ||
principals? |
principals?: string[]; |
Trusted caller principal set (host-resolved), e.g. |
['user:<id>', 'group:<gid>', 'world', 'entity:<selfPersonId>']. When set, |
||
| the plugin's read scope (profile injection / getContext) AND the read tools | ||
authorize by principal intersection (readPrincipals ∋ caller) instead of |
||
| legacy owner/group/world — so the agent sees facts/entities granted to the | ||
user's Person (entity: token), including ones it doesn't own. Omitted → |
||
| legacy scope (backward compatible). NEVER derived from LLM input — host- | ||
trusted, same provenance as groupId. |
||
timezone? |
timezone?: string; |
Viewer's IANA timezone (e.g. 'America/New_York'). When set, injected |
instants (events, recent-activity timestamps) and the user's **Timezone:** |
||
| label render in this zone instead of UTC. When absent, instants render as | ||
| stored (UTC, explicitly labelled). The host supplies this — the plugin never | ||
| reads entity metadata to discover it. | ||
userEntityPermissions? |
userEntityPermissions?: Permissions; |
Permissions stamped on the bootstrapped user entity. |
agentEntityPermissions? |
agentEntityPermissions?: Permissions; |
Permissions stamped on the bootstrapped agent entity. |
personaEntityId? |
personaEntityId?: EntityId; |
Optional persona entity id. When set, the agent's behavior rules are |
| read from and written to this entity instead of the variant agent entity | ||
bootstrapped by agentId. Lets several agent variants share one identity: |
||
pass the same personaEntityId to each variant and any rule written via |
||
memory_set_agent_rule (and any 'me'/'this_agent' subject token in read |
||
| tools) lands on the shared persona, so all variants render the same | ||
| "User-specific instructions for this agent" block. |
The variant agent entity (keyed by system_agent_id:<agentId>) is still
bootstrapped — host code that wants per-variant analytics or factContextIds
can reach it via getBootstrappedIds().agentEntityId. Only the LLM-facing
subject for rules + 'this_agent' is overridden.
The host owns the persona entity's lifecycle (upsert with whatever
type/identifier scheme fits — e.g. type:'assistant_persona' keyed by
{kind:'system_assistant_persona_id', value:userId} for a per-user
persona). The persona entity MUST be owned by the same userId passed
here, or memory_set_agent_rule will reject writes with a cross-owner
error (the same ownership invariant enforced for variant agents). |
| groupBootstrap? | groupBootstrap?: { /** Display name of the organization (e.g., the group's namefield). */ displayName: string; /** * Additional identifiers beyondsystem_group_id. For example: * [{ kind: 'domain', value: 'acme.com' }]lets signal extraction * converge with this same entity — the library'sEmailSignalAdapter * seeds organizations withkind: 'domain' when resolving participant * email addresses. Match that convention so bootstrap doesn't create a * parallel entity. */ identifiers?: Identifier[]; /** Permissions stamped on the bootstrapped organization entity. */ permissions?: Permissions; }; | Optional group ("current organization") bootstrap. When present AND
groupId is set, a third organization entity is upserted carrying the
identifier {kind: 'system_group_id', value: groupId} (plus any extras).
Rendered as an "About the User's Organization" block alongside the user
profile.
Visibility of facts on this entity is controlled by the host's
MemorySystem.visibilityPolicy and per-write permissions overrides —
the library's built-in defaults are group-read + world-read. A host
that needs per-user privacy on a shared org entity MUST install a
visibility policy that produces user-private permissions on those facts.
Skip this field (or leave groupId undefined) in non-grouped deployments
or when the caller has no current organization (e.g., platform superadmin). |
| userProfileInjection? | userProfileInjection?: MemoryPluginInjectionConfig; | Per-profile injection config. Defaults to {profile:true, topFacts:20}. |
| agentProfileInjection? | agentProfileInjection?: MemoryPluginInjectionConfig; | - |
| groupProfileInjection? | groupProfileInjection?: MemoryPluginInjectionConfig; | Injection config for the group ("organization") profile block. Same
defaults as userProfileInjection. Ignored when groupBootstrap is
not provided. |
| defaultVisibility? | defaultVisibility?: { forUser?: Visibility; forAgent?: Visibility; forOther?: Visibility; }; | Default visibility for memory_remember / memory_link. Defaults:
forUser='private', forAgent='group', forOther='private'. |
| autoResolveThreshold? | autoResolveThreshold?: number; | Fuzzy-match threshold for {surface} lookups. Default: 0.9. |
| userDisplayName? | userDisplayName?: string; | Entity display names used when bootstrapping. If the user/agent entity
already exists (identifier-keyed), these are ignored. The group entity's
displayName is taken from groupBootstrap.displayName. |
| agentDisplayName? | agentDisplayName?: string; | - |
MemoryPluginInjectionConfig interface
📍 src/core/context-nextgen/plugins/MemoryPluginNextGen.ts:87
Properties
| Property | Type | Description |
|---|---|---|
profile? |
profile?: boolean; |
Include profile.details text. Default: true. |
topFacts? |
topFacts?: number; |
Top N recent ranked facts to include. 0 disables. Default: 20. |
factPredicates? |
factPredicates?: string[]; |
Restrict topFacts to these predicates. Default: all. |
relatedTasks? |
relatedTasks?: boolean; |
Include active related tasks. Default: false. |
relatedEvents? |
relatedEvents?: boolean; |
Include recent related events. Default: false. |
identifiers? |
identifiers?: boolean; |
Include the entity's identifiers (kind=value). Default: false. |
maxFactLineChars? |
maxFactLineChars?: number; |
Optional cap on each rendered fact line. Default: undefined (no cap). |
| Previously defaulted to 200 chars, which clipped fact details mid-sentence | ||
| in the system message. Hosts who want to keep the system message small | ||
| can set this explicitly. See feedback_no_truncation.md. | ||
recentActivity? |
`recentActivity?: { |
/** How many rows to render. 0 disables. Default 20. */
limit?: number;
/** Lookback window in days. Default 7. */
windowDays?: number;
/** Optional predicate allowlist — e.g. `['committed_to','attended','decision_made']`. */
predicates?: string[];
};| Time-ordered recent activity about this entity. Renders a### Recent activitysection after the top facts. UnliketopFacts`
(ranked), this is strict newest-first — the agent sees a rolling window
of what the subject has actually been doing.
Defaults: ON with { limit: 20, windowDays: 7 }. Pass { limit: 0 }
to disable. |
MemorySystemConfig interface
Properties
| Property | Type | Description |
|---|---|---|
store |
store: IMemoryStore; |
- |
embedder? |
embedder?: IEmbedder; |
- |
profileGenerator? |
profileGenerator?: IProfileGenerator; |
- |
ruleEngine? |
ruleEngine?: IRuleEngine; |
- |
taskStates? |
taskStates?: TaskStatesConfig; |
Task-state vocabulary. Defaults to: |
| { active: ['pending','in_progress','blocked','deferred'], |
terminal: ['done','cancelled'] }
The two arrays must be disjoint and non-empty. |
| stateHistoryCap? | stateHistoryCap?: number; | Maximum number of entries retained in task.metadata.stateHistory.
transitionTaskState keeps the most-recent N entries (older entries are
dropped in FIFO order). Guards against unbounded metadata growth on chatty
tasks — a task that cycles through states thousands of times could
otherwise push the entity document past Mongo's 16 MB cap. Entries past
the cap are lost; callers that need unbounded audit must maintain it
externally.
Default: 200. Must be >= 1. |
| profileRegenerationThreshold? | profileRegenerationThreshold?: number; | Number of new atomic facts since last profile regen that triggers auto-regeneration. |
| topFactsRanking? | topFactsRanking?: RankingConfig; | - |
| embeddingQueue? | embeddingQueue?: EmbeddingQueueConfig; | - |
| entityResolution? | entityResolution?: EntityResolutionConfig; | - |
| predicates? | predicates?: import('./predicates/PredicateRegistry.js').PredicateRegistry; | Pluggable predicate vocabulary. When present, addFact canonicalizes the
predicate (camelCase/dash/alias → snake_case), applies defaultImportance
/ isAggregate defaults, auto-supersedes prior facts for singleValued
predicates, and folds registry weights into ranking. Absent = free-form
predicate strings (pre-registry behavior).
Pass PredicateRegistry.standard() for the built-in 44-predicate starter
set, PredicateRegistry.empty() plus .registerAll(...) for a fully
custom vocabulary. |
| predicateMode? | predicateMode?: 'permissive' | 'strict'; | 'strict' rejects any addFact whose (canonicalized) predicate is not in
the registry. 'permissive' (default) writes unknowns verbatim — they show
up in IngestionResult.newPredicates for drift monitoring.
Throws at construction if set to 'strict' without a registry. |
| unknownPredicatePolicy? | unknownPredicatePolicy?: 'fuzzy_map' | 'keep' | 'drop'; | H5 — how the LLM extraction pipeline handles a predicate that isn't in the
registry (after canonicalisation). Only used when a registry is configured.
'fuzzy_map'(default): try to snap onto the closest registered predicate via edit distance (length-aware — seeunknownPredicateFuzzyMaxDistance). Typical wins:work_at→works_at,mention→mentioned. No match → fall through to'keep'.'keep': write the unknown predicate verbatim (pre-H5 behaviour). Surfaces inIngestionResult.newPredicatesfor human review.'drop': skip the fact entirely, record the reason inunresolved.
Regardless of policy, the unknown predicate is always added to
newPredicates (with the mapping target if one was chosen) so operators
can still audit drift. |
| unknownPredicateFuzzyMaxDistance? | unknownPredicateFuzzyMaxDistance?: number; | F3 — absolute cap on the Levenshtein distance used by findClosest when
unknownPredicatePolicy='fuzzy_map'. The effective budget is also
clamped by predicate length (floor(min(lenA,lenB)/4), min 1), so
lowering this only tightens further. Default: 2 (registry's built-in
budget).
Tune down (e.g. 1) if your vocabulary has semantically-distinct
predicates within edit distance 2 of each other (talks_at vs
works_at). Tune up only with confidence your predicates are
well-separated in edit-distance space. |
| predicateAutoSupersede? | predicateAutoSupersede?: boolean; | When true (default), addFact auto-supersedes the prior visible fact for
a (subject, predicate) pair when the predicate is marked singleValued.
Set to false to opt out while still getting canonicalization + defaults. |
| onChange? | onChange?: (event: ChangeEvent) => void; | - |
| onError? | onError?: (error: unknown, event: ChangeEvent) => void; | Invoked when onChange throws. Lets operators surface listener failures
to their logging / telemetry pipeline rather than losing them silently.
Falls back to console.warn when unset. |
| visibilityPolicy? | visibilityPolicy?: VisibilityPolicy; | Host-supplied default permissions for every entity / fact write where
the caller didn't specify permissions explicitly. Lets hosts express
policies like "entities are group-readable, facts are owner-private by
default" in one place instead of at every call site — and, critically,
applies to writes emitted from inside the library (e.g., by
ExtractionResolver or ProfileGenerator paths) that the host can't
intercept directly.
Callers that pass permissions on the write input always win — the
policy only fills the blanks. Return undefined from the policy to
fall through to the library defaults (DEFAULT_GROUP_LEVEL /
DEFAULT_WORLD_LEVEL, both 'read').
Keep the function cheap — it runs on every entity / fact create. |
| entityContentComposers? | entityContentComposers?: Record< string, import('./composers/types.js').EntityContentComposer >; | Per-type content-embedding composers. Library ships defaults for task,
event, person, organization, topic, project, document,
cluster — see composers/defaults.ts. Caller-supplied entries override
the default for that type; unspecified types fall back to the default
(or get no content embedding if there is no default).
Composers run on every entity mutation site; contentEmbedding and
contentEmbeddingText refresh whenever the composed text changes.
Empty-string composer output skips the embed for that entity entirely. |
| factContentComposer? | factContentComposer?: import('./composers/types.js').FactContentComposer; | Fact content-embedding composer. Library ships a default that composes
atomic facts as "<subject.displayName> <predicate> <object.displayName | value>"
(resolving entity ids to surface forms) and document facts as their
details/summaryForEmbedding verbatim. Override to customize the
embedded fact text or to skip embedding for specific predicates (return
'' from compose). |
MemoryWritePluginConfig interface
📍 src/core/context-nextgen/plugins/MemoryWritePluginNextGen.ts:28
Properties
| Property | Type | Description |
|---|---|---|
memory |
memory: MemorySystem; |
Live memory system. REQUIRED. |
agentId |
agentId: string; |
Agent id. REQUIRED — matches MemoryPluginNextGen.agentId. |
userId |
userId: string; |
Current user id. REQUIRED — matches MemoryPluginNextGen.userId. |
groupId? |
groupId?: string; |
Trusted group id from host auth. Matches MemoryPluginNextGen.groupId. |
predicates? |
predicates?: PredicateRegistry; |
Optional explicit predicate registry override for prompt rendering. |
When unset, the plugin uses memory.getPredicateRegistry() — that's |
||
| almost always what you want, since the same registry governs | ||
| canonicalization, validation, and ranking on writes. Override only when | ||
| you intentionally want the LLM's advertised vocabulary to differ from | ||
| the storage-enforced one (rare; mostly testing). | ||
defaultVisibility? |
`defaultVisibility?: { |
forUser?: Visibility;
forAgent?: Visibility;
forOther?: Visibility;
};| Default visibility for remember/link. Matches MemoryPlugin defaults. | |autoResolveThreshold?|autoResolveThreshold?: number;| Fuzzy-match threshold for{surface}subject lookups. Default 0.9. | |getOwnSubjectIds?|getOwnSubjectIds?: () => { userEntityId?: string; agentEntityId?: string };| Callback supplied by the siblingMemoryPluginNextGenso"me"/"this_agent"tokens resolve to its bootstrapped entities. When absent, those tokens return "not available". | |forgetRateLimit?|forgetRateLimit?: { maxCallsPerWindow?: number; windowMs?: number };| Rate-limit override for memory_forget. Also used as the fallback for setAgentRuleRateLimitwhen that field is omitted. | |setAgentRuleRateLimit?|setAgentRuleRateLimit?: { maxCallsPerWindow?: number; windowMs?: number };| Rate-limit override for memory_set_agent_rule. Falls back to forgetRateLimitwhen omitted; allows hosts to give rule-writes a larger budget than destructive forgets without sharing one knob. | |forbiddenEntityTypes?|forbiddenEntityTypes?: ReadonlyArray<string>;| Entity types the agent must NOT create viamemory_upsert_entity. When set, such upserts are rejected and the tool description drops those types' guidance (see MemoryToolDeps.forbiddenEntityTypes). For host-deterministic types (e.g. event` from a calendar pipeline). Omit for no restriction. |
ModelTaskLimits interface
📍 src/core/model-execution/types.ts:9
Limits apply to one explicitly shared, process-local task.
Properties
| Property | Type | Description |
|---|---|---|
maxCalls? |
maxCalls?: number; |
- |
maxTokens? |
maxTokens?: number; |
Conservative reservations use provider capacity, not estimated prompt size. |
maxCostUSD? |
maxCostUSD?: number; |
Model token charges only. Requires a known conservative price bound. |
deadline? |
deadline?: number; |
Absolute Unix time in milliseconds. |
ModelTaskSnapshot interface
📍 src/core/model-execution/types.ts:127
Properties
| Property | Type | Description |
|---|---|---|
id |
id: string; |
- |
limits |
limits: ModelTaskLimits; |
- |
invocations |
invocations: ModelInvocationRecord[]; |
- |
attempts |
attempts: ModelAttemptRecord[]; |
- |
totals |
`totals: { |
calls: number;
inputTokens: number;
outputTokens: number;
totalTokens: number;
cachedInputTokens: number;
reasoningTokens: number;
knownModelCostUSD: number;
unknownUsageInvocations: number;
unknownCostInvocations: number;
unknownChargeAttempts: number;
chargedTokens: number;
chargedCostUSD: number | null;
};` | - |
MongoMemoryAdapterOptions interface
📍 src/memory/adapters/mongo/MongoMemoryAdapter.ts:81
Properties
| Property | Type | Description |
|---|---|---|
entities |
entities: IMongoCollectionLike<IEntity>; |
- |
facts |
facts: IMongoCollectionLike<IFact>; |
- |
factsArchive? |
factsArchive?: IMongoCollectionLike<IFact>; |
Optional archive collection for facts. When provided, the adapter |
| switches to move-on-archive semantics: |
updateFact(id, {archived: true}, scope)moves the doc fromfactstofactsArchive(stampingarchivedAt) instead of settingarchived: truein place.updateFact(id, {archived: false}, scope)(restoreFact) moves the doc back fromfactsArchivetofacts.getFact(id)falls back tofactsArchivewhen the id isn't infacts— supersession-chain walks and audit reads of archived facts keep working.findFacts(filter)withfilter.archived === truequeriesfactsArchive; otherwise queriesfacts(which stays live-only).countFacts(...)uses the same routing.
Benefits at scale: the live (primary) collection stays small even when
archive history is large; indexes don't carry archived rows; the
archived: false / $exists: false read filter becomes redundant on the
primary collection (every doc there is live by construction) and can be
dropped by callers.
When omitted, behaviour is unchanged — archived docs stay in the
primary collection with archived: true (legacy mode). |
| disableWorldVisibility? | disableWorldVisibility?: boolean; | When true, scopeToFilter omits the "world" branch
(groupId: {$ne: caller.groupId} ∧ permissions.world !== 'none') from
the produced read filter. Use this in deployments where cross-tenant
world-readable visibility is not used — the $ne predicate is
fundamentally not sargable and forces a wide collection scan on every
branch-equipped query (subject lookups, identifier lookups, traversal),
dwarfing any potential benefit when no docs actually carry
permissions.world: 'read'. Default false (backwards-compatible).
Security note: enabling this changes the visibility contract. Any
record carrying permissions.world: 'read' becomes invisible to
cross-group callers — silently from the caller's perspective. The
adapter emits a one-time console.warn at construction so the
trade-off isn't fully silent operationally; pass a logger to route
the warning through your own infrastructure instead. |
| logger? | logger?: { warn(msg: string): void }; | Optional logger used for boot-time advisories (currently: the
disableWorldVisibility security trade-off warning). Defaults to
console.warn. Pass your app's logger to redirect into structured
logging. |
| useNativeGraphLookup? | useNativeGraphLookup?: boolean; | When true AND facts.aggregate is present, traverse() uses a single
native $graphLookup pipeline per direction instead of iterative BFS.
Default: false. |
| vectorIndexName? | vectorIndexName?: string; | When set AND facts.aggregate is present, semanticSearch() uses Atlas
Vector Search via $vectorSearch against this index name. Otherwise
falls back to cursor-scan cosine. |
| entityVectorIndexName? | entityVectorIndexName?: string; | When set AND entities.aggregate is present, semanticSearchEntities()
uses Atlas Vector Search via $vectorSearch against this index name.
Otherwise falls back to cursor-scan cosine over entity.identityEmbedding.
Index is NOT auto-created by ensureIndexes() (which only handles regular
b-tree indexes). Create it via ensureVectorSearchIndexes() (programmatic,
requires mongodb node driver v6.6+ + Atlas Server v6.0.11+) or via the
Atlas UI / admin API. See ensureVectorSearchIndexes JSDoc for details. |
| entityContentVectorIndexName? | entityContentVectorIndexName?: string; | Atlas Vector Search index for entity.contentEmbedding — used when
semanticSearchEntities is called with opts.embeddingField:'content'
(currently consumed by MemorySystem.searchDocuments). Distinct from
entityVectorIndexName so identity matches never leak into content
search and vice versa. Same auto-creation rules as the identity index:
not built by ensureIndexes(); create via ensureVectorSearchIndexes(). |
| vectorCandidateMultiplier? | vectorCandidateMultiplier?: number; | Number of vector candidates to ask Atlas Vector Search to consider before
returning topK. Used by both semanticSearch (facts) and
semanticSearchEntities when the corresponding index name is set.
Default: topK * 10. |
| factsCollectionName? | factsCollectionName?: string; | Name of the facts collection — required by $graphLookup (it needs the
collection name to recurse over). If omitted, useNativeGraphLookup is
disabled and iterative BFS is used instead. |
| defaultPageSize? | defaultPageSize?: number; | Default page size when a caller doesn't specify limit. |
| vectorIndexDrainPollMs? | vectorIndexDrainPollMs?: number; | When ensureVectorSearchIndexes must drop + recreate a drifted index, Atlas
removes the dropped index asynchronously and rejects a same-name create
until it has fully drained. The helper polls until the dropped index is gone
before recreating. These tune that wait. Defaults: poll every 2s, give up
after 120s (then fall through to create + its race handling). Lower them in
tests; raise the timeout for unusually slow Atlas tiers. |
| vectorIndexDrainTimeoutMs? | vectorIndexDrainTimeoutMs?: number; | - |
NewTaskSummary interface
📍 src/memory/integration/signalReconciliationPrompt.ts:50
A newly-observed/created task rendered as context (no id needed).
Properties
| Property | Type | Description |
|---|---|---|
title |
title: string; |
- |
state? |
state?: string; |
- |
dueAt? |
dueAt?: string; |
- |
evidenceQuote? |
evidenceQuote?: string; |
- |
Plan interface
📍 src/domain/entities/Task.ts:390
Execution plan - a goal with steps to achieve it
Properties
| Property | Type | Description |
|---|---|---|
id |
id: string; |
- |
goal |
goal: string; |
- |
context? |
context?: string; |
- |
tasks |
tasks: Task[]; |
- |
concurrency? |
concurrency?: PlanConcurrency; |
Concurrency settings |
allowDynamicTasks |
allowDynamicTasks: boolean; |
Can agent modify the plan? |
status |
status: PlanStatus; |
Plan status |
suspendedReason? |
`suspendedReason?: { |
type: 'waiting_external' | 'manual_pause' | 'error';
taskId?: string;
message?: string;
};| Why is the plan suspended? | |createdAt|createdAt: number;| Timestamps | |startedAt?|startedAt?: number;| - | |completedAt?|completedAt?: number;| - | |lastUpdatedAt|lastUpdatedAt: number;| - | |currentTaskId?|currentTaskId?: string;| For resume: which task to continue from | |metadata?|metadata?: Record<string, unknown>;` | Metadata |
PlanConcurrency interface
📍 src/domain/entities/Task.ts:374
Plan concurrency settings
Properties
| Property | Type | Description |
|---|---|---|
maxParallelTasks |
maxParallelTasks: number; |
- |
strategy |
`strategy: 'fifo' | 'priority' |
failureMode? |
`failureMode?: 'fail-fast' | 'continue' |
- 'fail-fast': Stop on first failure (Promise.all behavior) - DEFAULT
- 'continue': Continue other tasks on failure, mark failed ones
- 'fail-all': Wait for all to complete, then report all failures together |
PlanInput interface
📍 src/domain/entities/Task.ts:429
Input for creating a plan
Properties
| Property | Type | Description |
|---|---|---|
goal |
goal: string; |
- |
context? |
context?: string; |
- |
tasks |
tasks: TaskInput[]; |
- |
concurrency? |
concurrency?: PlanConcurrency; |
- |
allowDynamicTasks? |
allowDynamicTasks?: boolean; |
- |
metadata? |
metadata?: Record<string, unknown>; |
- |
skipCycleCheck? |
skipCycleCheck?: boolean; |
Skip dependency cycle detection (default: false) |
PlanningAgentConfig interface
📍 src/capabilities/taskAgent/PlanningAgent.ts:16
PlanningAgent configuration
Properties
| Property | Type | Description |
|---|---|---|
connector |
`connector: string | Connector;` |
model |
model: string; |
Model to use for planning (can be different/cheaper than execution) |
maxPlanningIterations? |
maxPlanningIterations?: number; |
Max planning iterations |
planningTemperature? |
planningTemperature?: number; |
Temperature for planning (lower = more deterministic) |
availableTools? |
availableTools?: ToolFunction[]; |
Tools available for the plan (used to inform planning) |
RelatedTask interface
Properties
| Property | Type | Description |
|---|---|---|
task |
task: IEntity; |
- |
role |
role: string; |
Relationship that links this task to the subject entity |
| (e.g. 'assigned_to', 'reporter_of', 'project_of', 'context_of'). |
ResearchPlan interface
📍 src/capabilities/researchAgent/types.ts:179
Research plan for systematic research
Properties
| Property | Type | Description |
|---|---|---|
goal |
goal: string; |
Research goal/question |
queries |
queries: ResearchQuery[]; |
Queries to execute |
sources? |
sources?: string[]; |
Sources to use (empty = all available) |
maxResultsPerQuery? |
maxResultsPerQuery?: number; |
Maximum results per query |
maxTotalFindings? |
maxTotalFindings?: number; |
Maximum total findings |
ResolverMemoryHooks interface
📍 src/memory/resolution/EntityResolver.ts:95
Narrow hook used by EntityResolver — lets it query + upsert without pulling in the full MemorySystem surface (keeps the resolver easy to test).
Properties
| Property | Type | Description |
|---|---|---|
store |
store: IMemoryStore; |
- |
embedQuery? |
embedQuery?: (text: string) => Promise<number[]>; |
- |
upsertEntity |
`upsertEntity: ( |
input: Partial<IEntity> & {
identifiers: Identifier[];
displayName: string;
type: string;
},
scope: ScopeFilter,
) => Promise<{ entity: IEntity; created: boolean }>;| - | |atomicCreateOrResolve|atomicCreateOrResolve: (
input: Partial<IEntity> & {
identifiers: Identifier[];
displayName: string;
type: string;
/** Surface + any caller-supplied aliases (used for alias accumulation on race-loss). /
aliasesForMerge?: string[];
metadataMerge?: 'fillMissing' | 'overwrite';
/* Persistent multi-entity binding — written to IEntity.contextIds. */
contextIds?: EntityId[];
},
scope: ScopeFilter,
) => Promise<{ entity: IEntity; created: boolean }>;| Atomically create OR resolve by(type, normalizedDisplayName, scope), applying alias accumulation when an existing row is returned. Used by upsertBySurfacewhen resolver Tier 1-3 didn't match — replaces the naiveupsertEntitycall that raced past concurrent inserts of the same surface. SeeMemorySystem.tryAtomicCreateOrResolvefor the wiring. | |appendAliasesAndIdentifiers|appendAliasesAndIdentifiers: (
id: EntityId,
aliases: string[],
identifiers: Identifier[],
scope: ScopeFilter,
opts?: {
metadata?: Record<string, unknown>;
metadataMerge?: 'fillMissing' | 'overwrite';
contextIdsToUnion?: EntityId[];
},
) => Promise<IEntity>;| Patches an existing entity with additional aliases/identifiers (no-op if already present). Whenopts.metadatais supplied, merges peropts.metadataMerge`:
'fillMissing'(default): only keys absent from stored metadata are set.'overwrite': shallow-merge (incoming keys win). Whenopts.contextIdsToUnionis supplied, the helper unions those entity ids intoIEntity.contextIds(dedupe + self-reference filter). Caller is responsible for visibility-validating the additions. |
RoutineTaskResult interface
📍 src/domain/entities/RoutineExecutionRecord.ts:46
Properties
| Property | Type | Description |
|---|---|---|
success |
success: boolean; |
- |
output? |
output?: string; |
- |
error? |
error?: string; |
- |
validationScore? |
validationScore?: number; |
- |
validationExplanation? |
validationExplanation?: string; |
- |
RoutineTaskSnapshot interface
📍 src/domain/entities/RoutineExecutionRecord.ts:54
Properties
| Property | Type | Description |
|---|---|---|
taskId |
taskId: string; |
- |
name |
name: string; |
- |
description |
description: string; |
- |
status |
`status: 'pending' | 'blocked' |
attempts |
attempts: number; |
- |
maxAttempts |
maxAttempts: number; |
- |
result? |
result?: RoutineTaskResult; |
- |
startedAt? |
startedAt?: number; |
- |
completedAt? |
completedAt?: number; |
- |
controlFlowType? |
`controlFlowType?: 'map' | 'fold' |
RoutineTaskSummary interface
📍 src/core/routines/types.ts:23
Properties
| Property | Type | Description |
|---|---|---|
id |
readonly id: string; |
- |
executionId? |
readonly executionId?: string; |
- |
name |
readonly name: string; |
- |
state |
`readonly state: 'pending' | 'running' |
verification? |
readonly verification?: VerificationDecision; |
- |
references? |
readonly references?: readonly EvidenceReference[]; |
- |
SerializedInContextMemoryState interface
📍 src/core/context-nextgen/plugins/InContextMemoryPluginNextGen.ts:51
Properties
| Property | Type | Description |
|---|---|---|
entries |
entries: InContextEntry[]; |
- |
SerializedWorkingMemoryState interface
📍 src/core/context-nextgen/plugins/WorkingMemoryPluginNextGen.ts:50
Properties
| Property | Type | Description |
|---|---|---|
version |
version: number; |
- |
entries |
`entries: Array<{ |
key: string;
description: string;
value: unknown;
scope: MemoryScope;
sizeBytes: number;
basePriority?: MemoryPriority;
pinned?: boolean;
}>;` | - |
Task interface
📍 src/domain/entities/Task.ts:297
A single unit of work
Properties
| Property | Type | Description |
|---|---|---|
id |
id: string; |
- |
name |
name: string; |
- |
description |
description: string; |
- |
status |
status: TaskStatus; |
- |
dependsOn |
dependsOn: string[]; |
Tasks that must complete before this one (task IDs) |
externalDependency? |
externalDependency?: ExternalDependency; |
External dependency (if waiting on external event) |
condition? |
condition?: TaskCondition; |
Condition for execution |
execution? |
execution?: TaskExecution; |
Execution settings |
validation? |
validation?: TaskValidation; |
Completion validation settings |
suggestedTools? |
suggestedTools?: string[]; |
Tool names the LLM should prefer for this task (advisory, not enforced) |
expectedOutput? |
expectedOutput?: string; |
Optional expected output description |
controlFlow? |
controlFlow?: TaskControlFlow; |
Control flow: map, fold, or until (replaces normal LLM execution for this task) |
result? |
`result?: { |
success: boolean;
output?: unknown;
error?: string;
/** Validation score (0-100) if validation was performed */
validationScore?: number;
/** Explanation of validation result */
validationExplanation?: string;
};| Result after completion | |createdAt|createdAt: number;| Timestamps | |startedAt?|startedAt?: number;| - | |completedAt?|completedAt?: number;| - | |lastUpdatedAt|lastUpdatedAt: number;| - | |attempts|attempts: number;| Retry tracking | |maxAttempts|maxAttempts: number;| - | |metadata?|metadata?: Record<string, unknown>;` | Metadata for extensions |
TaskCondition interface
📍 src/domain/entities/Task.ts:61
Task condition - evaluated before execution
Properties
| Property | Type | Description |
|---|---|---|
memoryKey |
memoryKey: string; |
- |
operator |
operator: ConditionOperator; |
- |
value? |
value?: unknown; |
- |
onFalse |
`onFalse: 'skip' | 'fail' |
TaskExecution interface
📍 src/domain/entities/Task.ts:175
Task execution settings
Properties
| Property | Type | Description |
|---|---|---|
parallel? |
parallel?: boolean; |
Can run in parallel with other parallel tasks |
maxConcurrency? |
maxConcurrency?: number; |
Max concurrent if this spawns sub-work |
priority? |
priority?: number; |
Priority (higher = executed first) |
maxIterations? |
maxIterations?: number; |
Maximum LLM iterations (tool-call loops) per agent.run() for this task. |
| Prevents runaway agents. Default: 50. | ||
raceProtection? |
raceProtection?: boolean; |
If true (default), re-check condition immediately before LLM call |
| to protect against race conditions when parallel tasks modify memory. | ||
| Set to false to skip re-check for performance if you know condition won't change. |
TaskFailure interface
📍 src/domain/errors/AIErrors.ts:330
Task failure info for parallel execution
Properties
| Property | Type | Description |
|---|---|---|
taskId |
taskId: string; |
- |
taskName |
taskName: string; |
- |
error |
error: Error; |
- |
TaskFoldFlow interface
📍 src/domain/entities/Task.ts:138
Fold: accumulate a result across array elements
Properties
| Property | Type | Description |
|---|---|---|
type |
type: 'fold'; |
- |
source |
source: ControlFlowSource; |
Source array reference — task name, memory key, or structured ref. |
tasks |
tasks: SubRoutineSpec; |
Sub-routine to run per element |
initialValue |
initialValue: unknown; |
Starting accumulator value |
resultKey |
resultKey: string; |
Memory key for final accumulated result |
maxIterations? |
maxIterations?: number; |
Cap iterations (default: array.length, hard max: 1000) |
iterationTimeoutMs? |
iterationTimeoutMs?: number; |
Timeout per sub-execution iteration in ms (default: no timeout) |
TaskInput interface
📍 src/domain/entities/Task.ts:355
Input for creating a task
Properties
| Property | Type | Description |
|---|---|---|
id? |
id?: string; |
- |
name |
name: string; |
- |
description |
description: string; |
- |
dependsOn? |
dependsOn?: string[]; |
- |
externalDependency? |
externalDependency?: ExternalDependency; |
- |
condition? |
condition?: TaskCondition; |
- |
execution? |
execution?: TaskExecution; |
- |
suggestedTools? |
suggestedTools?: string[]; |
- |
validation? |
validation?: TaskValidation; |
- |
expectedOutput? |
expectedOutput?: string; |
- |
controlFlow? |
controlFlow?: TaskControlFlow; |
- |
maxAttempts? |
maxAttempts?: number; |
- |
metadata? |
metadata?: Record<string, unknown>; |
- |
TaskMapFlow interface
📍 src/domain/entities/Task.ts:123
Map: execute a sub-routine for each element in an array
Properties
| Property | Type | Description |
|---|---|---|
type |
type: 'map'; |
- |
source |
source: ControlFlowSource; |
Source array reference — task name, memory key, or structured ref. |
tasks |
tasks: SubRoutineSpec; |
Sub-routine to run per element |
resultKey? |
resultKey?: string; |
Memory key for collected results array |
maxIterations? |
maxIterations?: number; |
Cap iterations (default: array.length, hard max: 1000) |
iterationTimeoutMs? |
iterationTimeoutMs?: number; |
Timeout per sub-execution iteration in ms (default: no timeout) |
TaskReconciliationOp interface
📍 src/memory/integration/ExtractionResolver.ts:154
Task reconciliation operation — the signal-reconciliation pass (a SECOND LLM pass that runs after extraction). Distinct from the fact ops above: tasks are MUTABLE entities, so reconciliation UPDATES them in place (state, narrative, dueAt, assignee) instead of the archive+create supersession used for immutable facts.
taskId MUST be one of the prior task ids passed to
MemorySystem.applyReconciliationOps; hallucinated ids are rejected.
When newState moves the task into a TERMINAL state, the dispatcher stamps
AI-resolution provenance onto the task metadata (see AIResolutionProvenance)
so the host can distinguish "auto-resolved by AI reconciliation" from "closed
by the user". reason is REQUIRED whenever newState is set.
Properties
| Property | Type | Description |
|---|---|---|
op |
op: 'task_update'; |
- |
taskId |
taskId: EntityId; |
- |
newState? |
newState?: string; |
New task state, e.g. `'in_progress' |
narrative? |
narrative?: string; |
Refreshed task narrative / card body. Optional. |
dueAt? |
dueAt?: string; |
ISO date string for a (re)scheduled due date. Optional. |
assigneeId? |
assigneeId?: string; |
Reassigned owner entity id. Optional. |
evidenceQuote? |
evidenceQuote?: string; |
Verbatim quote from the signal content justifying the change. |
reason? |
reason?: string; |
Brief reasoning — REQUIRED when newState is set (the provenance trail). |
TaskSourceRef interface
📍 src/domain/entities/Task.ts:110
Reference to a source value for control flow operations.
Properties
| Property | Type | Description |
|---|---|---|
task? |
task?: string; |
Reference the output of a named task (resolves to _task_output{name}) |
key? |
key?: string; |
Direct memory key lookup |
path? |
path?: string; |
JSON path to extract from the resolved value (e.g., 'data.items', 'results[0].entries') |
TaskStateAction interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:34
Properties
| Property | Type | Description |
|---|---|---|
authority |
authority: 'observation'; |
- |
outcome |
outcome: Omit<ToolExecutionOutcome, 'error'>; |
- |
confirmation? |
confirmation?: { status: TaskStatus; source: TaskStateSource }; |
Explicit host confirmation of workflow state; tool success alone never sets it. |
TaskStateHistoryEntry interface
📍 src/memory/MemorySystem.ts:264
Single entry appended to task.metadata.stateHistory on every transition.
No cap — retention is the caller's problem (audit systems, GDPR, archival).
Properties
| Property | Type | Description |
|---|---|---|
from |
`from: string | undefined;` |
to |
to: string; |
- |
at |
at: Date; |
- |
signalId? |
signalId?: string; |
- |
reason? |
reason?: string; |
- |
TaskStateHostDelta interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:64
Properties
| Property | Type | Description |
|---|---|---|
notes? |
notes?: TaskStateNoteInput[]; |
- |
resolve? |
resolve?: string[]; |
- |
confirmActions? |
confirmActions?: Array<{ executionId: string; status: TaskStatus; source: TaskStateSource }>; |
Explicit host confirmations, applied atomically before optional pruning. |
prune? |
prune?: { noteIds?: string[]; executionIds?: string[] }; |
Explicit pruning after notes/resolutions/confirmations; only the final state must fit. |
TaskStateNote interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:24
Properties
| Property | Type | Description |
|---|---|---|
authority |
`authority: 'host' | 'proposal' |
resolved? |
resolved?: boolean; |
- |
supersededBy? |
supersededBy?: string; |
- |
TaskStateNoteInput interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:15
Properties
| Property | Type | Description |
|---|---|---|
id |
id: string; |
- |
category |
category: TaskStateCategory; |
- |
text |
text: string; |
- |
source |
source: TaskStateSource; |
- |
uncertainty? |
uncertainty?: string; |
- |
supersedes? |
supersedes?: string; |
- |
conflictsWith? |
conflictsWith?: string[]; |
- |
TaskStatePluginConfig interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:51
Properties
| Property | Type | Description |
|---|---|---|
scope |
scope: EvidenceScope; |
- |
taskId |
taskId: string; |
- |
association? |
association?: string; |
- |
lifecycle? |
lifecycle?: TaskStateLifecycleBinding; |
- |
maxEntries? |
maxEntries?: number; |
Combined notes/actions; default 100. No automatic eviction. |
maxPromptTokens? |
maxPromptTokens?: number; |
Full protected prompt view; default 8000 estimated tokens. |
maxSnapshotBytes? |
maxSnapshotBytes?: number; |
Strict JSON snapshot/input limit; default 256 KiB. |
TaskStatesConfig interface
Task-state vocabulary configuration. Drives which states getContext
surfaces as "open" in relatedTasks, and which are treated as "terminal"
(done/cancelled) for side effects like auto-setting completedAt.
Library default preserves the legacy vocabulary. Apps using different
lifecycles (e.g. 'proposed' | 'scheduled' | 'in_progress' | 'done')
override here rather than hardcoding state strings in metadata queries.
Properties
| Property | Type | Description |
|---|---|---|
active |
active: string[]; |
States treated as "still open" — surfaced in getContext.relatedTasks. |
terminal |
terminal: string[]; |
States treated as "finished" — transitions into these auto-set completedAt. |
TaskStateSnapshot interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:40
Properties
| Property | Type | Description |
|---|---|---|
version |
version: 1; |
- |
scope |
scope: EvidenceScope; |
- |
taskId |
taskId: string; |
- |
association? |
association?: string; |
Optional host-owned session/work association, checked on restoration. |
revision |
revision: number; |
- |
notes |
notes: TaskStateNote[]; |
- |
actions |
actions: TaskStateAction[]; |
- |
lifecycle? |
lifecycle?: TaskStateLifecycleView; |
- |
TaskStateSource interface
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:14
Properties
| Property | Type | Description |
|---|---|---|
label |
label: string; |
- |
turnId? |
turnId?: string; |
- |
evidence? |
evidence?: EvidenceReference |
- |
TaskUntilFlow interface
📍 src/domain/entities/Task.ts:155
Until: repeat a sub-routine until a condition is met
Properties
| Property | Type | Description |
|---|---|---|
type |
type: 'until'; |
- |
tasks |
tasks: SubRoutineSpec; |
Sub-routine to run each iteration |
condition |
condition: TaskCondition; |
Checked AFTER each iteration (reuses existing TaskCondition type) |
maxIterations? |
maxIterations?: number; |
Maximum iterations. Default: 1. Hard cap: 1000. |
iterationKey? |
iterationKey?: string; |
Optional ICM key for current iteration index |
iterationTimeoutMs? |
iterationTimeoutMs?: number; |
Timeout per sub-execution iteration in ms (default: no timeout) |
TaskValidation interface
📍 src/domain/entities/Task.ts:208
Task completion validation settings
Used to verify that a task actually achieved its goal before marking it complete. Supports multiple validation approaches:
- Programmatic checks (memory keys, hooks)
- LLM self-reflection with completeness scoring
- Natural language criteria evaluation
Properties
| Property | Type | Description |
|---|---|---|
completionCriteria? |
completionCriteria?: string[]; |
Natural language completion criteria. |
| These are evaluated by LLM self-reflection to determine if the task is complete. | ||
| Examples: |
- "The response contains at least 3 specific examples"
- "User's email has been validated and stored in memory"
- "All requested data fields are present in the output"
This is the RECOMMENDED approach for flexible, intelligent validation. |
| minCompletionScore? | minCompletionScore?: number; | Minimum completeness score (0-100) to consider task successful.
LLM self-reflection returns a score; if below this threshold:
- If requireUserApproval is set, ask user
- Otherwise, follow the mode setting (strict = fail, warn = continue)
Default: 80 |
|
requireUserApproval?|requireUserApproval?: 'never' | 'uncertain' | 'always';| When to require user approval: - 'never': Never ask user, use automated decision (default)
- 'uncertain': Ask user when score is between minCompletionScore and minCompletionScore + 15
- 'always': Always ask user to confirm task completion |
|
requiredMemoryKeys?|requiredMemoryKeys?: string[];| Memory keys that must exist after task completion. If the task should store data in memory, list the required keys here. This is a hard requirement checked BEFORE LLM reflection. | |customValidator?|customValidator?: string;| Custom validation function name (registered via validateTask hook). The hook will be called with this identifier to dispatch to the right validator. Runs AFTER LLM reflection, can override the result. | |mode?|mode?: 'strict' | 'warn';| Validation mode: - 'strict': Validation failure marks task as failed (default)
- 'warn': Validation failure logs warning but task still completes |
|
skipReflection?|skipReflection?: boolean;| Skip LLM self-reflection validation. LLM validation is opt-in: set tofalseto enable it (requires completionCriteria). Default: undefined (treated as true — validation auto-passes). |
TaskValidationResult interface
📍 src/domain/entities/Task.ts:270
Result of task validation (returned by LLM reflection)
Properties
| Property | Type | Description |
|---|---|---|
isComplete |
isComplete: boolean; |
Whether the task is considered complete |
completionScore |
completionScore: number; |
Completeness score from 0-100 |
explanation |
explanation: string; |
LLM's explanation of why the task is/isn't complete |
criteriaResults? |
`criteriaResults?: Array<{ |
criterion: string;
met: boolean;
evidence?: string;
}>;| Per-criterion evaluation results | |requiresUserApproval|requiresUserApproval: boolean;| Whether user approval is needed | |approvalReason?|approvalReason?: string;` | Reason for requiring user approval |
TransitionTaskStateOptions interface
📍 src/memory/MemorySystem.ts:272
Properties
| Property | Type | Description |
|---|---|---|
signalId? |
signalId?: string; |
Stable audit pointer — typically the ingest signal id. Flows into the history entry AND the written fact. |
at? |
at?: Date; |
When the transition happened. Defaults to now. |
reason? |
reason?: string; |
Free-form audit note. Stored in the history entry. |
validate? |
`validate?: 'strict' | 'warn' |
'warn'(default): any transition allowed; out-of-matrix transitions route throughonErrorand proceed.'strict': out-of-matrix transitions throwInvalidTaskTransitionError— the metadata write is skipped.'none': no validation, no warnings. | |transitions?|transitions?: Record<string, string[]>;| Explicit allowed transitions:{ from: [allowed, to, states] }. When omitted, every transition is allowed (with a warning for duplicates in'warn'). Keys include'__initial'for transitions into a first-time state. |
TransitionTaskStateResult interface
📍 src/memory/MemorySystem.ts:294
Properties
| Property | Type | Description |
|---|---|---|
task |
task: IEntity; |
- |
rejected? |
rejected?: string; |
Set when validate='strict' rejected the transition. |
WorkingMemoryAccess interface
📍 src/domain/interfaces/IToolContext.ts:26
Limited memory access for tools
This interface is designed to work with all agent types:
- Basic agents: Use simple scopes ('session', 'persistent')
- TaskAgent: Use task-aware scopes ({ type: 'task', taskIds: [...] })
- UniversalAgent: Switches between simple and task-aware based on mode
Methods
get()
get(key: string): Promise<unknown>;
Parameters:
key:string
Returns: Promise<unknown>
set()
Store a value in memory
set(
key: string,
description: string,
value: unknown,
options?: {
/** Scope determines lifecycle - defaults to 'session' */
scope?: MemoryScope;
/** Base priority for eviction ordering */
priority?: MemoryPriority;
/** If true, entry is never evicted */
pinned?: boolean;
}
): Promise<void>;
Parameters:
key:stringdescription:stringvalue:unknownoptions:{ scope?: MemoryScope | undefined; priority?: MemoryPriority | undefined; pinned?: boolean | undefined; } | undefined(optional)
Returns: Promise<void>
delete()
delete(key: string): Promise<void>;
Parameters:
key:string
Returns: Promise<void>
has()
has(key: string): Promise<boolean>;
Parameters:
key:string
Returns: Promise<boolean>
list()
List all memory entries Returns key, description, and computed priority info
list(): Promise<
Array<{
key: string;
description: string;
effectivePriority?: MemoryPriority;
pinned?: boolean;
}>
>;
Returns: Promise<{ key: string; description: string; effectivePriority?: MemoryPriority | undefined; pinned?: boolean | undefined; }[]>
WorkingMemoryConfig interface
📍 src/domain/entities/Memory.ts:424
Configuration for working memory
Properties
| Property | Type | Description |
|---|---|---|
maxSizeBytes? |
maxSizeBytes?: number; |
Max memory size in bytes. If not set, calculated from model context |
maxIndexEntries? |
maxIndexEntries?: number; |
Max number of entries in the memory index. Excess entries are auto-evicted via LRU. Default: 30 |
descriptionMaxLength |
descriptionMaxLength: number; |
Max description length |
softLimitPercent |
softLimitPercent: number; |
Percentage at which to warn agent |
contextAllocationPercent |
contextAllocationPercent: number; |
Percentage of model context to allocate to memory |
WorkingMemoryEvents interface
📍 src/capabilities/taskAgent/WorkingMemory.ts:83
Properties
| Property | Type | Description |
|---|---|---|
stored |
stored: { key: string; description: string; scope: MemoryScope }; |
- |
retrieved |
retrieved: { key: string }; |
- |
deleted |
deleted: { key: string }; |
- |
evicted |
`evicted: { keys: string[]; reason: 'lru' | 'size' |
limit_warning |
limit_warning: { utilizationPercent: number }; |
- |
stale_entries |
stale_entries: { entries: StaleEntryInfo[] }; |
- |
WorkingMemoryPluginConfig interface
📍 src/core/context-nextgen/plugins/WorkingMemoryPluginNextGen.ts:65
Properties
| Property | Type | Description |
|---|---|---|
config? |
config?: WorkingMemoryConfig; |
Memory configuration |
storage? |
storage?: IMemoryStorage; |
Storage backend (default: InMemoryStorage) |
priorityCalculator? |
priorityCalculator?: PriorityCalculator; |
Priority calculator (default: staticPriorityCalculator) |
ControlFlowSource type
📍 src/domain/entities/Task.ts:120
Source can be a simple key string (legacy) or a structured reference.
type ControlFlowSource = string | TaskSourceRef
EvictionStrategy type
📍 src/capabilities/taskAgent/WorkingMemory.ts:73
Eviction strategy type
type EvictionStrategy = 'lru' | 'size'
MemoryPriority type
📍 src/domain/entities/Memory.ts:110
Priority determines eviction order (lower priority evicted first)
- critical: Never evicted (pinned, or actively in use)
- high: Important data, evicted only when necessary
- normal: Default priority
- low: Candidate for eviction (stale data, completed task data)
type MemoryPriority = 'critical' | 'high' | 'normal' | 'low'
MemoryScope type
📍 src/domain/entities/Memory.ts:33
Union type - memory system accepts both
type MemoryScope = SimpleScope | TaskAwareScope
MemorySystemWithConnectorsConfig type
📍 src/memory/integration/createMemorySystemWithConnectors.ts:24
type MemorySystemWithConnectorsConfig = Omit<
MemorySystemConfig,
'embedder' | 'profileGenerator'
> & {
connectors?: MemoryConnectorsConfig;
}
PlanStatus type
📍 src/domain/entities/Task.ts:38
Plan status
type PlanStatus = | 'pending'
| 'running'
| 'suspended'
| 'completed'
| 'failed'
| 'cancelled'
SimpleScope type
📍 src/domain/entities/Memory.ts:20
Memory entities for WorkingMemory
This module provides a GENERIC memory system that works across all agent types:
- Basic Agent: Simple session/persistent scoping with static priority
- TaskAgent: Task-aware scoping with dynamic priority based on task states
- UniversalAgent: Mode-aware, switches strategy based on current mode
The key abstraction is PriorityCalculator - a pluggable strategy that determines entry priority for eviction decisions.
type SimpleScope = 'session' | 'persistent'
SubRoutineSpec type
📍 src/domain/entities/Task.ts:107
Sub-routine specification: either inline tasks or a full RoutineDefinition
type SubRoutineSpec = TaskInput[] | RoutineDefinition
TaskAwareScope type
📍 src/domain/entities/Memory.ts:25
Task-aware scope for TaskAgent/UniversalAgent
type TaskAwareScope = | { type: 'task'; taskIds: string[] } // Needed for specific task(s)
| { type: 'plan' } // Needed throughout plan execution
| { type: 'persistent' }
TaskControlFlow type
📍 src/domain/entities/Task.ts:170
Union of all control flow types
type TaskControlFlow = TaskMapFlow | TaskFoldFlow | TaskUntilFlow
TaskStateCategory type
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:13
type TaskStateCategory = 'objective' | 'constraint' | 'decision' | 'question' | 'artifact'
TaskStateLifecycleBinding type
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:29
type TaskStateLifecycleBinding = { kind: 'task' | 'routine' | 'memory'; id: string }
TaskStateLifecycleView type
📍 src/core/context-nextgen/plugins/TaskStatePluginNextGen.ts:30
type TaskStateLifecycleView = | { kind: 'task'; id: string; status?: TaskStatus }
| { kind: 'routine'; id: string; status?: RoutineExecutionStatus }
| { kind: 'memory'; id: string; state?: string | null }
TaskStatus type
📍 src/domain/entities/Task.ts:13
Task status lifecycle
type TaskStatus = | 'pending' // Not started
| 'blocked' // Dependencies not met
| 'in_progress' // Currently executing
| 'waiting_external' // Waiting on external event
| 'completed' // Successfully finished
| 'failed' // Failed after max retries
| 'skipped' // Skipped (condition not met)
| 'cancelled'
TaskStatusForMemory type
📍 src/domain/entities/Memory.ts:219
Task status values for priority calculation
type TaskStatusForMemory = 'pending' | 'in_progress' | 'completed' | 'failed' | 'skipped' | 'cancelled'
calculateEntrySize function
📍 src/domain/entities/Memory.ts:539
Calculate the size of a value in bytes (JSON serialization) Uses Buffer.byteLength for accurate UTF-8 byte count
export function calculateEntrySize(value: unknown): number
canTaskExecute function
📍 src/domain/entities/Task.ts:548
Check if a task can be executed (dependencies met, status is pending)
export function canTaskExecute(task: Task, allTasks: Task[]): boolean
createInContextMemoryContributor function
📍 src/core/context-nextgen/assembly.ts:204
Optional adapter: select whiteboard entries without deleting their stored source.
export function createInContextMemoryContributor(plugin: InContextMemoryPluginNextGen): ContextContributor
createMemoryReadTools function
📍 src/tools/memory/index.ts:146
Read-only retrieval tools — no memory writes performed.
export function createMemoryReadTools(args: CreateMemoryToolsArgs): ToolFunction[]
createMemorySystemWithConnectors function
📍 src/memory/integration/createMemorySystemWithConnectors.ts:31
export function createMemorySystemWithConnectors(
config: MemorySystemWithConnectorsConfig,
): MemorySystem
createMemoryTools function
📍 src/tools/memory/index.ts:176
All 12 memory tools (6 read + 6 write). Convenience factory — most callers
should prefer createMemoryReadTools / createMemoryWriteTools separately
so read agents don't carry the write-tool schema overhead.
export function createMemoryTools(args: CreateMemoryToolsArgs): ToolFunction[]
createMemoryWriteTools function
📍 src/tools/memory/index.ts:159
Write-side memory tools — mutate entities and facts.
export function createMemoryWriteTools(args: CreateMemoryToolsArgs): ToolFunction[]
createPlan function
📍 src/domain/entities/Task.ts:481
Create a plan with tasks
export function createPlan(input: PlanInput): Plan
createTask function
📍 src/domain/entities/Task.ts:452
Create a task with defaults
export function createTask(input: TaskInput): Task
createTaskSnapshots function
📍 src/domain/entities/RoutineExecutionRecord.ts:102
Create initial task snapshots from a routine definition.
export function createTaskSnapshots(definition: RoutineDefinition): RoutineTaskSnapshot[]
detectDependencyCycle function
📍 src/domain/entities/Task.ts:755
Detect dependency cycles in tasks using depth-first search
export function detectDependencyCycle(tasks: Task[]): string[] | null
evaluateCondition function
📍 src/domain/entities/Task.ts:612
Evaluate a task condition against memory
export async function evaluateCondition(
condition: TaskCondition,
memory: ConditionMemoryAccess
): Promise<boolean>
forPlan function
📍 src/domain/entities/Memory.ts:487
Create a plan-scoped memory entry input
export function forPlan(
key: string,
description: string,
value: unknown,
options?: { priority?: MemoryPriority; pinned?: boolean }
): MemoryEntryInput
forTasks function
📍 src/domain/entities/Memory.ts:467
Create a task-scoped memory entry input
export function forTasks(
key: string,
description: string,
value: unknown,
taskIds: string[],
options?: { priority?: MemoryPriority; pinned?: boolean }
): MemoryEntryInput
generateSimplePlan function
📍 src/capabilities/taskAgent/PlanningAgent.ts:438
Simple plan generation without tools (fallback)
export async function generateSimplePlan(
goal: string,
context?: string
): Promise<Plan>
getNextExecutableTasks function
📍 src/domain/entities/Task.ts:570
Get the next tasks that can be executed
export function getNextExecutableTasks(plan: Plan): Task[]
getTaskDependencies function
📍 src/domain/entities/Task.ts:709
Get the dependency tasks for a task
export function getTaskDependencies(task: Task, allTasks: Task[]): Task[]
isSimpleScope function
📍 src/domain/entities/Memory.ts:45
Type guard: is this a simple scope?
export function isSimpleScope(scope: MemoryScope): scope is SimpleScope
isTaskAwareScope function
📍 src/domain/entities/Memory.ts:38
Type guard: is this a task-aware scope?
export function isTaskAwareScope(scope: MemoryScope): scope is TaskAwareScope
isTaskBlocked function
📍 src/domain/entities/Task.ts:688
Check if a task is blocked by dependencies
export function isTaskBlocked(task: Task, allTasks: Task[]): boolean
isTerminalMemoryStatus function
📍 src/domain/entities/Memory.ts:229
Check if a task status is terminal (task will not progress further)
export function isTerminalMemoryStatus(status: TaskStatusForMemory): boolean
isTerminalStatus function
📍 src/domain/entities/Task.ts:31
Check if a task status is terminal (task will not progress further)
export function isTerminalStatus(status: TaskStatus): boolean
resolveDependencies function
📍 src/domain/entities/Task.ts:723
Resolve task name dependencies to task IDs Modifies taskInputs in place
export function resolveDependencies(taskInputs: TaskInput[], tasks: Task[]): void
scopeEquals function
📍 src/domain/entities/Memory.ts:53
Compare two scopes for equality Handles both simple scopes (string comparison) and task-aware scopes (deep comparison)
export function scopeEquals(a: MemoryScope, b: MemoryScope): boolean
scopeMatches function
📍 src/domain/entities/Memory.ts:83
Check if a scope matches a filter scope More flexible than scopeEquals - supports partial matching for task scopes
export function scopeMatches(entryScope: MemoryScope, filterScope: MemoryScope): boolean
updateTaskStatus function
📍 src/domain/entities/Task.ts:660
Update task status and timestamps
export function updateTaskStatus(task: Task, status: TaskStatus): Task
DEFAULT_CHECKPOINT_STRATEGY const
📍 src/capabilities/taskAgent/CheckpointManager.ts:25
Properties
| Property | Type | Description |
|---|---|---|
afterToolCalls |
1 |
- |
afterLLMCalls |
1 |
- |
intervalMs |
30000 |
- |
beforeExternalWait |
true |
- |
mode |
'async' |
- |
DEFAULT_MEMORY_CONFIG const
📍 src/domain/entities/Memory.ts:506
Default configuration values
Properties
| Property | Type | Description |
|---|---|---|
maxSizeBytes |
25 * 1024 * 1024 |
- |
maxIndexEntries |
30 |
- |
descriptionMaxLength |
500 |
- |
softLimitPercent |
80 |
- |
contextAllocationPercent |
20 |
- |
MEMORY_PRIORITY_VALUES const
📍 src/domain/entities/Memory.ts:115
Priority values for comparison (higher = more important, less likely to evict)
Properties
| Property | Type | Description |
|---|---|---|
critical |
4 |
- |
high |
3 |
- |
normal |
2 |
- |
low |
1 |
- |