How to: Manage Agent Sessions

Goal: Create, persist, resume, fork, rewind, tag, and delete agent sessions using SessionManager.


Core types

#![allow(unused)]
fn main() {
pub struct SessionMetadata {
    pub id: String,           // UUID
    pub name: Option<String>,
    pub tags: Vec<String>,
    pub agent_name: String,
    pub created_at: i64,      // Unix milliseconds
    pub updated_at: i64,      // Unix milliseconds
    pub turn_count: u32,
    pub total_tokens: u64,
}

pub struct Session {
    pub metadata: SessionMetadata,
    pub messages: Vec<serde_json::Value>,  // conversation history
    pub state: serde_json::Value,          // arbitrary agent state
}
}

InMemorySessionManager

The built-in implementation. All data is lost when the process exits. Use it for tests or ephemeral agents; use the checkpoint-backed implementation for persistence.

#![allow(unused)]
fn main() {
use synwire_agent::session::manager::InMemorySessionManager;
use synwire_core::agents::session::{Session, SessionManager, SessionMetadata};

let mgr = InMemorySessionManager::new();
}

Save and resume

#![allow(unused)]
fn main() {
use serde_json::json;

// Construct a new session.
let session = Session {
    metadata: SessionMetadata {
        id: uuid::Uuid::new_v4().to_string(),
        name: Some("my-task".to_string()),
        tags: vec!["production".to_string()],
        agent_name: "code-agent".to_string(),
        created_at: 0,
        updated_at: 0,
        turn_count: 0,
        total_tokens: 0,
    },
    messages: Vec::new(),
    state: json!({"cwd": "/home/user"}),
};

mgr.save(&session).await?;

// Later — in the same or a new call — resume by ID.
let loaded = mgr.resume(&session.metadata.id).await?;
}

save sets updated_at to the current time. Calling save on an existing ID updates it in place.


List sessions

Returns all sessions ordered by updated_at descending (most recently active first).

#![allow(unused)]
fn main() {
let sessions: Vec<SessionMetadata> = mgr.list().await?;
for s in &sessions {
    println!("{} {:?} turns={}", s.id, s.name, s.turn_count);
}
}

Fork a session

Creates an independent copy with a new UUID. The fork shares history up to the fork point but diverges independently afterwards.

#![allow(unused)]
fn main() {
let fork_meta = mgr.fork(&session.metadata.id, Some("experiment-branch".to_string())).await?;
assert_ne!(fork_meta.id, session.metadata.id);
}

Pass None as the name to auto-generate a name of the form "<original name> (fork)".


Rewind to a previous turn

Truncates the message list to turn_index entries (zero-based). Useful for re-running from a specific point without forking.

#![allow(unused)]
fn main() {
// Keep only the first 3 messages.
let rewound = mgr.rewind(&session.metadata.id, 3).await?;
assert_eq!(rewound.messages.len(), 3);
}

Tag a session

Adds one or more tags without duplicates. Existing tags are preserved.

#![allow(unused)]
fn main() {
mgr.tag(&session.metadata.id, vec!["reviewed".to_string(), "archived".to_string()]).await?;
}

Rename a session

#![allow(unused)]
fn main() {
mgr.rename(&session.metadata.id, "final-delivery".to_string()).await?;
}

Delete a session

#![allow(unused)]
fn main() {
mgr.delete(&session.metadata.id).await?;
// Subsequent resume returns AgentError::Session.
}

Implementing SessionManager

To persist sessions to a database or remote store, implement the SessionManager trait. All methods return BoxFuture so async storage drivers are supported.

#![allow(unused)]
fn main() {
use synwire_core::agents::session::{Session, SessionManager, SessionMetadata};
use synwire_core::agents::error::AgentError;
use synwire_core::BoxFuture;

struct RedisSessionManager { /* ... */ }

impl SessionManager for RedisSessionManager {
    fn list(&self) -> BoxFuture<'_, Result<Vec<SessionMetadata>, AgentError>> {
        Box::pin(async move { /* query Redis ZREVRANGE */ todo!() })
    }

    fn resume(&self, session_id: &str) -> BoxFuture<'_, Result<Session, AgentError>> {
        let id = session_id.to_string();
        Box::pin(async move { /* GET id */ todo!() })
    }

    fn save(&self, session: &Session) -> BoxFuture<'_, Result<(), AgentError>> {
        let session = session.clone();
        Box::pin(async move { /* SET id */ todo!() })
    }

    fn delete(&self, session_id: &str) -> BoxFuture<'_, Result<(), AgentError>> {
        let id = session_id.to_string();
        Box::pin(async move { /* DEL id */ todo!() })
    }

    fn fork(&self, session_id: &str, new_name: Option<String>) -> BoxFuture<'_, Result<SessionMetadata, AgentError>> {
        todo!()
    }

    fn rewind(&self, session_id: &str, turn_index: u32) -> BoxFuture<'_, Result<Session, AgentError>> {
        todo!()
    }

    fn tag(&self, session_id: &str, tags: Vec<String>) -> BoxFuture<'_, Result<(), AgentError>> {
        todo!()
    }

    fn rename(&self, session_id: &str, new_name: String) -> BoxFuture<'_, Result<(), AgentError>> {
        todo!()
    }
}
}

See also