Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Use Shared State

Use this when agents need to share persistent state across thread boundaries, agent types, or delegation trees. Shared state lives in the ProfileStore and is addressed by a typed namespace (ProfileKey) and a key (&str), giving you fine-grained control over who sees what.

Prerequisites

  • A working awaken agent runtime (see First Agent)
  • A ProfileStore backend configured on the runtime (e.g. file store or Postgres)

Concepts

Shared state has two dimensions:

DimensionTypePurpose
NamespaceProfileKeyDefines what is stored — a compile-time binding between a static string key (KEY) and a typed Value. Each key is registered once per plugin via register_profile_key.
Key&str (or StateScope helper)Defines which instance — a runtime string that partitions storage. Different keys isolate or share data between agents and threads.

Together, (ProfileKey::KEY, key: &str) uniquely identifies a shared state entry in the profile store.

Steps

1. Define a shared state key

Create a struct that implements ProfileKey. The KEY constant is the namespace; the Value type is what gets serialized.

use serde::{Deserialize, Serialize};
use awaken_contract::ProfileKey;

#[derive(Clone, Default, Serialize, Deserialize)]
pub struct TeamContext {
    pub goal: String,
    pub constraints: Vec<String>,
}

pub struct TeamContextKey;

impl ProfileKey for TeamContextKey {
    const KEY: &'static str = "team_context";
    type Value = TeamContext;
}

2. Register in a plugin

Inside your plugin’s register method, call register_profile_key on the registrar.

use awaken_contract::StateError;
use awaken_runtime::plugins::registry::PluginRegistrar;

fn register(&self, r: &mut PluginRegistrar) -> Result<(), StateError> {
    r.register_profile_key::<TeamContextKey>()?;
    Ok(())
}

3. Read and write in a hook

In any phase hook, obtain ProfileAccess from the context and use read / write with a key string. StateScope is a convenience builder for common key patterns — call .as_str() to get the key.

use awaken_contract::StateScope;

async fn execute(&self, ctx: &mut PhaseContext) -> Result<(), anyhow::Error> {
    let profile = ctx.profile().expect("ProfileStore not configured");
    let identity = ctx.snapshot().run_identity();

    // Build a scope key from the current agent's parent thread
    let scope = match &identity.parent_thread_id {
        Some(pid) => StateScope::parent_thread(pid),
        None => StateScope::global(),
    };

    // Read (returns TeamContext::default() if missing)
    let mut team: TeamContext = profile.read::<TeamContextKey>(scope.as_str()).await?;

    // Mutate and write back
    team.goal = "Ship the feature".into();
    profile.write::<TeamContextKey>(scope.as_str(), &team).await?;

    Ok(())
}

4. Choose the right scope

StateScope has several constructors. Pick the one that matches your sharing pattern:

ScenarioScopeExample
All agents across all threadsStateScope::global()Org-wide configuration
All agents spawned from the same parent threadStateScope::parent_thread(id)A delegation tree sharing context
All instances of the same agent typeStateScope::agent_type(name)Planner agents sharing learned heuristics
Single thread onlyStateScope::thread(id)Thread-local scratchpad
Custom partitionStateScope::new("custom-key")Any application-defined grouping

You can also pass any raw &str directly — StateScope is optional convenience.

When to use shared state

MechanismLifetimeScopeBest for
StateKeySingle run (in-memory snapshot)One agent threadTransient per-run state (counters, flags, accumulated context)
ProfileKey with agent/system keyPersistent (profile store)Per-agent or systemPer-agent or per-user settings that don’t cross boundaries
ProfileKey with StateScope keyPersistent (profile store)Any StateScope stringCross-agent, cross-thread persistent state

Use ProfileKey with a StateScope key when state must survive across runs and be visible to agents in different threads or of different types.

Common Errors

SymptomCauseFix
profile key not registered: <ns>Key not registered in any pluginCall r.register_profile_key::<YourKey>() in the plugin’s register method
Always reads Value::default()Writing and reading use different key stringsVerify both sides construct the same StateScope or use the same &str key
Data leaks between scopesUsing StateScope::global() when a narrower scope is neededSwitch to parent_thread, agent_type, or thread scope

Key Files

PathPurpose
crates/awaken-contract/src/contract/shared_state.rsStateScope type
crates/awaken-contract/src/contract/profile_store.rsProfileKey trait, ProfileOwner enum
crates/awaken-runtime/src/profile/mod.rsProfileAccess with read, write, delete methods
crates/awaken-runtime/src/plugins/registry.rsPluginRegistrar::register_profile_key registration