This document refines the state and memory boundaries for the agrun.js MVP.
It should be read together with agrun_docs/spec.md.
The goal is to keep state, memory, output, and observation clearly separated.
If those boundaries blur, the runtime will quickly become harder to reason about and harder to extend.
The most important rule is:
State is not memory.
Memory is not state.
state belongs to the current run.
It answers questions such as:
- what run is currently executing
- what skill was selected
- what the latest observation is
- whether the run is running, finished, or failed
State is:
current-run
ephemeral
execution-oriented
memory belongs to the runtime instance across runs.
It answers questions such as:
- what important observations were retained from earlier runs
- what later skills can read as historical context
Memory is:
cross-run
append-only
history-oriented
session history is a separate layer from runtime memory.
It answers questions such as:
- what the user and assistant said in one session
- which recent turns should be replayed for provider-backed follow-up turns
Session history is:
session-scoped
message-oriented
prompt-window oriented
Session-backed provider turns now separate continuity into two canonical layers:
session memory
inquiry context
session memory is the durable semantic layer rebuilt from summary, confirmed memory entries, and recent transcript.
It contains facts, preferences, decisions, summary/compacted context, and recent turns.
inquiry context is the session-scoped working set for the active investigation.
It contains the active goal/topic/query, candidate and selected sources, last search/read state, and clarification working state.
Per-run intent-like helpers may still exist as temporary runtime projections. They are not canonical continuity state and should never be persisted as durable memory.
The runtime now exposes these layers through runState.contextSnapshot:
contextSnapshot.sessionMemory
contextSnapshot.inquiryContext
runState.readAttemptSignal
sessionContextView remains as a compatibility alias for debug consumers, but contextSnapshot plus read-only run signals are the canonical continuity model.
Compatibility rule:
- transitional fields such as
turnIntentmay still appear in debug projections while the codebase converges - those fields are compatibility-only and must not become a source of truth
The runtime should preserve direction before preserving volume.
The most important continuity fields are:
activeGoalactiveTopicactiveQuerypendingClarificationlastClarificationResolutionselectedSourcelastReadSource
These fields together define where the current session is going.
If prompt compaction or continuity reduction happens, these fields should be protected before:
- older transcript
- low-value free memory
- generic history
- debug-oriented projections
Direction rule:
Do not preserve more context.
Preserve the most important direction.
pendingClarification is the only canonical unresolved-clarification state.
Derived fields such as:
clarificationStatusopenAmbiguityambiguityState
may still be exposed for planner, evaluate, or debug views, but they must be derived from:
pendingClarificationlastClarificationResolution
They must not become separate stored truth.
Clarification should stay a last resort.
Do not create a persistent clarification state when:
- the missing input is optional
- a low-risk direct tool path already exists
- the runtime can safely apply a default
To keep continuity understandable and prevent direction drift:
- do not add a separate semantic pre-pass as canonical turn truth
- do not duplicate goal/topic/ambiguity across multiple independent state layers
- do not let debug projections become planner truth
- do not let free-text ambiguity summaries outrank structured clarification objects
- do not let compaction logic infer new task semantics; compaction should only shrink context
The MVP should keep state intentionally small and explicit.
RunState is the runtime's internal state for one run() call.
Suggested fields:
runId
status
stepCount
cycleCount
phase
selectedSkill
observation
error
oodae
Rules:
RunStateis per-run onlyRunStateshould not become a general history objectRunStateshould not own a separate canonicaloutputfield in the MVPphaseshould expose the currently active OODAE phaseoodae.cyclesshould keep cycle-scoped observe/orient/decide/act/evaluate records
Why no output field in RunState:
- the skill's direct
outputalready belongs inRunResult observationis the runtime-owned interpretation used for state and memory work- duplicating both in
RunStatemakes the model harder to keep coherent
RuntimeState is runtime-instance scoped.
Suggested MVP field:
lastRun
lastRun should be a small summary of the most recent run.
It should not become full run history.
These three layers must remain separate.
output is the direct result returned by the selected skill.
It belongs to:
skill execution result
external run result
observation is the runtime's normalized interpretation of that execution outcome.
It belongs to:
runtime bookkeeping
state update
memory decision
memory entry is the long-lived record the runtime chooses to append for future runs.
It belongs to:
cross-run history
runtime-instance memory
Correct flow:
skill returns output
↓
runtime derives observation
↓
runtime decides whether to append memory
The MVP memory model should be:
append-only
in-memory
runtime-instance scoped
This is intentionally not:
a database
a vector store
a knowledge graph
a tracing backend
The runtime owns the canonical memory entry shape.
Skills do not write arbitrary objects directly into the underlying memory store.
MVP contract:
- skills read memory through
context.memory.readAll() - skills request memory writes through
helpers.appendMemory(entry) - the runtime normalizes that request into the canonical stored memory entry
This preserves a controlled boundary:
- skills can use memory
- runtime keeps schema ownership and append policy
Not every run event should become semantic memory.
MVP default policy:
- successful observations may produce memory entries
- explicit skill requests through
helpers.appendMemory(entry)may produce memory entries - failed runs do not append semantic memory entries by default
Failure information should still be visible through:
error- failure
observation stepstracelastRunsummary
This keeps memory focused on cross-run context rather than turning it into a generic trace log.
These three concepts must remain separate:
session history: raw multi-turn messages for one sessionmemory: retained semantic facts for later runs or later turnsknowledge: external retrieval or document context supplied by skills
The session MVP should only add:
- session history
- session-scoped semantic memory
- structured session evidence for planner/provider reconstruction
- internal compaction summaries for prompt-window control
It should not add:
- a built-in knowledge base
- retrieval persistence inside runtime core
- a vector index inside the runtime kernel
Session memory should not stop at persistence. For conversational continuity, the runtime needs a closed loop:
session history
↓
semantic memory extraction
↓
structured evidence snapshot
↓
planner / provider prompt reconstruction
↓
direct answer or clarification
Rules:
- session history stores raw
userandassistantmessages - session memory stores high-value confirmed facts, preferences, and decisions
- prompt reconstruction should expose those as separate evidence sections, not one loose memory blob
- recall-style questions should check confirmed session evidence before choosing clarification
- failed turns and unresolved ambiguity should not be promoted into confirmed session memory
The runtime owns state transitions.
That means:
- skills return
output - runtime derives
observation - runtime updates
RunState - runtime updates
RuntimeState.lastRun
Skills must not define or directly mutate runtime state structure.
During skill execution, the runtime may expose both:
- current run state through
context.state - prior retained history through
context.memory.readAll()
This gives the skill two distinct views:
- what is happening now
- what was retained from earlier runs
Those views should remain separate.
The state and memory model can be summarized as:
stateis for the current run.memoryis for cross-run continuity.- the runtime owns state transitions.
- the runtime owns memory structure and append policy.
output,observation, andmemory entryare different layers.- append-only memory is the right MVP model.
- memory policy is domain-neutral; host-specific policy plugs in via hooks.
Memory promotion, sensitivity filtering, and recall selection must stay generic. The runtime has no business-domain knowledge and must not grow any.
- Sensitive-content filters in
global-memory.jscover only universal credential patterns (api keys, tokens, passwords, bearer strings). Customer names, invoice numbers, patient ids, account balances, and other domain PII are the host's responsibility. - Confidence thresholds, kind classifications, and slot schemas are tuned to be defensible for any host, not optimized for one.
- Hosts that need stricter filtering, validation against domain
records, or redaction of business data should use runtime-provided
extension points (e.g. a
sensitivityFilterhook) rather than asking the runtime to learn their schema. - New memory-promotion logic that would require the runtime to
understand a specific business concept is a signal the work
belongs in a host hook, not in
agrun.js.
Hosts that do not want any cross-session promotion can pass
createRuntime({ globalMemory: { enabled: false } }). When disabled
the runtime:
- skips the per-turn semantic extraction LLM call,
- does not read from the
globalMemoryIndexedDB store at turn start, - does not promote any extracted entries.
Per-session memory (memoryEntries writes from skills via
helpers.appendMemory or the memory-skill "remember:" command)
keeps working. Existing globalMemory rows stay on disk; toggling
enabled back to true restores recall and promotion. See
public-runtime-api.md "Disabling Global Memory"
for the full contract.
promoteToGlobalMemory() refuses to persist entries whose text, metadata,
or key names match a known secret shape. The filter covers generic labels
(api_key, password, authorization, x-api-key, aws_secret_access_key,
…), provider-specific key formats (sk-…, sk-ant-…, AIza…, ya29.…,
ghp_/gho_/ghu_/ghs_/ghr_…, AKIA…), PEM private keys, and JWT triples.
The scan recurses into nested objects and arrays with cycle protection; a
key named apiKey, client_secret, or bearer blocks the entry regardless
of the value. See src/session/global-memory.js
and the fixture list in
test/unit/global-memory-sensitive.test.js.
createSessionStore() wraps the primary IndexedDB store with
createResilientSessionStore(). A single
fatal storage error (Safari private mode InvalidStateError, quota
exhaustion, permission denial) swaps the primary with an in-memory fallback
for the remainder of the session and fires onStorageDegraded({ reason, method })
via the session store options. Non-fatal errors continue to throw so callers
can retry. Session continuity within the page survives the degrade; durability
across reloads does not (the host UI should surface the telemetry to warn the
user).
agrun.js stays clean when it keeps these boundaries:
RunState = current execution state
RuntimeState = last run summary for the instance
Output = direct skill result
Observation = runtime interpretation
Memory = retained cross-run context
The smaller and clearer these boundaries stay, the easier the runtime will be to implement, test, and extend.