Search documentation

Browse Awaken Agents docs
Docs/Awaken Agentsv1.0.0-dev/Internal mechanisms/UnderstandKeep State Across Runs
Note·You're reading pre-release documentation (v1.0.0-dev). Interfaces and behavior may change before a stable release.

Internal mechanisms · Understand

Keep State Across Runs

What this page covers

Use Thread state for later Runs, and put cross-Thread data behind an application-owned Resource.

Use Scope::Thread when a later Run on the same Thread needs a value. Do not use Scope::Shared or Scope::Profile to share values between Threads. Current committed-state readers always rebuild commands for one ThreadId; those scope variants only distinguish addresses inside that Thread’s state.

Choose the owner before writing code:

NeedOwner
scratch state for one Run and its resumesRuntime Scope::Run
state needed by later Runs on one ThreadRuntime Scope::Thread
business data shared by users, Agents, or Threadsapplication store exposed through a Resource and Tool
a durable message delivered to another Threadrun-ingress Outbox

Keep a value on one Thread

Prefer a typed key so schema drift fails closed.

use awaken_agent_contract::agent::state::{MergePolicy, Scope, StateKey};
use serde::{Deserialize, Serialize};

#[derive(Default, Serialize, Deserialize)]
struct ReviewState { approved: bool }

struct ReviewStateKey;

impl StateKey for ReviewStateKey {
    const KEY: &'static str = "review_state";
    const SCOPE: Scope = Scope::Thread;
    const MERGE: MergePolicy = MergePolicy::Disjoint;
    type Value = ReviewState;
}

let command = ReviewStateKey::try_write(&ReviewState { approved: true })?;

Attach the command to the Tool output or hook reaction that caused the change. The Runtime commits it with the same transition. On the next Run, rebuild the Thread Store from CommittedThreadView::committed_state(thread_id) and call ReviewStateKey::load.

sequenceDiagram
    participant Run1 as Run 1
    participant Commit as ThreadCommit
    participant Facts as Facts for Thread A
    participant Run2 as Run 2 on Thread A

    Run1->>Commit: stage Scope::Thread command
    Commit->>Facts: commit transition and command
    Run2->>Facts: committed_state(Thread A)
    Facts-->>Run2: replayed Store
    Run2->>Run2: typed load

Put cross-Thread data outside Runtime state

If Thread A and Thread B must read the same customer record, task board, or profile, make that record application-owned. Inject a Resource that holds the client or repository and expose bounded read/write operations through a Tool. The Tool’s permission policy remains the execution grant.

flowchart LR
    A[Thread A] --> TA[Authorized Tool]
    B[Thread B] --> TB[Authorized Tool]
    TA --> R[Application Resource]
    TB --> R
    R --> D[(Application-owned shared store)]

    A -. durable message .-> O[Run-ingress Outbox]
    O -. idempotent target append .-> B

This separation gives the shared record one owner, one concurrency policy, and one migration path. It also avoids copying customer data into every Thread fact log. Use the Outbox when the requirement is delivery, not shared querying.

Choose merge behavior inside one commit

PolicyUse when
Disjointone intended producer owns the key; a later value replaces
Commutativeindependent JSON object fields may shallow-merge
Exclusivea second Set in the same batch must reject the batch

These policies do not coordinate writers across Threads or processes. The application-owned shared store must define its own transaction or optimistic concurrency rule.

Act only when state cannot be interpreted

If StateKey::load returns StateError, migrate the persisted value to the declared schema before resuming the Thread. If an Exclusive batch is rejected, remove the duplicate producer or choose the policy that matches the actual writers. Normal replay, an absent key, and Outbox retry do not require repair.