Working with Sessions

Sessions are created automatically by the Runner and contain the full conversation history.

TypeScript

import { Agent, Runner } from '@autohandai/agent-sdk';

const agent = new Agent({
  name: "File Analyzer",
  instructions: "Analyze files and provide insights.",
});

const result = await Runner.run(agent, "What files are in src/?");
const session = result.session;

// Inspect the full conversation
for (const msg of session.messages) {
  console.log(`[${msg.role}] ${msg.content.substring(0, 100)}`);
}

Python

from autohand_agents import Agent, Runner

agent = Agent(
    name="File Analyzer",
    instructions="Analyze files and provide insights.",
)

result = Runner.run_sync(agent, "What files are in src/?")
session = result.session

# Inspect the full conversation
for msg in session.messages:
    print(f"[{msg.role}] {msg.content[:100]}")

Java

import com.autohand.Agent;
import com.autohand.Runner;

Agent agent = new Agent.Builder()
    .name("File Analyzer")
    .instructions("Analyze files and provide insights")
    .build();

RunResult result = Runner.runSync(agent, "What files are in src/?");
Session session = result.getSession();

// Inspect the full conversation
for (Message msg : session.getMessages()) {
    System.out.println("[" + msg.getRole() + "] " + msg.getContent().substring(0, 100));
}

Go

package main

import (
    "fmt"
    "github.com/autohandai/agentsdk-go"
)

func main() {
    agent := agentsdk.NewAgent(
        "File Analyzer",
        "Analyze files and provide insights",
    )
    
    result := agentsdk.RunnerRunSync(agent, "What files are in src/?")
    session := result.Session
    
    // Inspect the full conversation
    for _, msg := range session.Messages {
        fmt.Printf("[%s] %s
", msg.Role, msg.Content[:100])
    }
}

Swift

import AutohandAgents

let agent = Agent(
    name: "File Analyzer",
    instructions: "Analyze files and provide insights"
)

let result = try await Runner.run(agent, prompt: "What files are in src/?")
let session = result.session

// Inspect the full conversation
for msg in session.messages {
    print("[(msg.role)] (String(msg.content.prefix(100)))")
}

Rust

use autohand_agents::{Agent, Runner};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = Agent::new("File Analyzer", "Analyze files and provide insights");
    
    let result = Runner::run_sync(&agent, "What files are in src/?")?;
    let session = &result.session;
    
    // Inspect the full conversation
    for msg in &session.messages {
        println!("[{}] {}", msg.role, &msg.content[..100.min(msg.content.len())]);
    }
    
    Ok(())
}

Session Attributes

A Session has these attributes:

  • id — auto-generated unique identifier (e.g., session-20250407-143022-123456)
  • messages — list of Message objects in chronological order
  • working_directory — the directory relative paths resolve to (default .)
  • created_at — when the session was created

Coordinate concurrent TypeScript sessions

The TypeScript SDK can observe other Autohand sessions working in the same local workspace and warn about likely Git, file-claim, or repository-drift conflicts. This is local-machine coordination, not a remote lock service.

import { Agent } from '@autohandai/agent-sdk';

const agent = await Agent.create({
  cwd: '/path/to/project',
  sessions: { awareness: 'coordinate' },
});

try {
  const peers = await agent.getSessionPeers();
  console.log(peers);
} finally {
  await agent.close();
}
TierBehaviorUse it when
passive Publishes activity and reports peer presence. Your host wants visibility but applies its own policy.
warn (default) Adds advisory warnings for likely Git conflicts, path overlap, and repository drift. You want safe guidance without an approval pause.
coordinate Uses the normal permission flow before writing a path claimed by another active agent. Multiple agents edit the same workspace and a human or host can decide.

Inspect peers and events

getSessionPeers() is available on Agent, AutohandSDK, and RPCClient. It returns other live records and excludes the caller's own CLI session.

  • session_peer_joined — another session appears.
  • session_peer_updated — meaningful activity, status, or claim data changes; heartbeat timestamp noise is ignored.
  • session_peer_left — a previously visible session disappears.
  • session_awareness_error — the registry could not be observed; the event is recoverable.

Coordination and configuration safety

At the coordinate tier, a conflicting write is acknowledged and then allowed or denied through the standard permission protocol. Auto-confirm, --yes, YOLO, and unrestricted modes continue with a warning instead of adding an interactive pause.

When the SDK receives an explicit tier, it copies the effective CLI configuration to a private temporary file with 0600 permissions, overlays only sessions.awareness, and removes the file at shutdown. It never mutates the user's config file.

Registry boundaries

Active records live under $AUTOHAND_HOME/active-agents, which defaults to ~/.autohand/active-agents. Treat records as untrusted local input: the SDK strips unsafe terminal and directional controls, limits activity text and path collections, and does not let a reader delete another session's record.

Saving and Loading Sessions

Persist a session to disk and resume it later.

TypeScript

import { Session } from '@autohandai/agent-sdk';
import fs from 'fs/promises';

// Save
await session.save("/tmp/my-session.json");

// Load
const loaded = await Session.load("/tmp/my-session.json");
console.log(`Loaded session ${loaded.id} with ${loaded.messages.length} messages`);

Python

from autohand_agents import Session

# Save
session.save("/tmp/my-session.json")

# Load
loaded = Session.load("/tmp/my-session.json")
print(f"Loaded session {loaded.id} with {len(loaded.messages)} messages")

Java

import com.autohand.Session;

// Save
session.save("/tmp/my-session.json");

// Load
Session loaded = Session.load("/tmp/my-session.json");
System.out.println("Loaded session " + loaded.getId() + " with " + loaded.getMessages().size() + " messages");

Go

package main

import (
    "fmt"
    "github.com/autohandai/agentsdk-go"
)

// Save
session.Save("/tmp/my-session.json")

// Load
loaded := agentsdk.SessionLoad("/tmp/my-session.json")
fmt.Printf("Loaded session %s with %d messages
", loaded.ID, len(loaded.Messages))

Swift

import AutohandAgents
import Foundation

// Save
try session.save(to: URL(fileURLWithPath: "/tmp/my-session.json"))

// Load
let loaded = try Session.load(from: URL(fileURLWithPath: "/tmp/my-session.json"))
print("Loaded session \(loaded.id) with \(loaded.messages.count) messages")

Rust

use autohand_agents::Session;

// Save
session.save("/tmp/my-session.json")?;

// Load
let loaded = Session::load("/tmp/my-session.json")?;
println!("Loaded session {} with {} messages", loaded.id, loaded.messages.len());

The saved JSON file contains the message history, session ID, working directory, and creation timestamp. You can inspect it with any JSON viewer — it's not opaque binary.

Manual Session Manipulation

Sessions can be built up manually before passing to a runner.

TypeScript

import { Session, Message } from '@autohandai/agent-sdk';

const session = new Session();
session.addUserMessage("Analyze this code:");
session.addAssistantMessage("Sure, I'll look at it now.");
session.addUserMessage("Thanks, start with main.py");

// Now pass the session to the runner
// (Runner currently creates its own Session, but future
// versions will accept a pre-built session for resumption)

Python

from autohand_agents import Session, Message

session = Session()
session.add_user_message("Analyze this code:")
session.add_assistant_message("Sure, I'll look at it now.")
session.add_user_message("Thanks, start with main.py")

# Now pass the session to the runner
# (Runner currently creates its own Session, but future
# versions will accept a pre-built session for resumption)

Java

import com.autohand.Session;
import com.autohand.Message;

Session session = new Session();
session.addUserMessage("Analyze this code:");
session.addAssistantMessage("Sure, I'll look at it now.");
session.addUserMessage("Thanks, start with main.java");

// Now pass the session to the runner
// (Runner currently creates its own Session, but future
// versions will accept a pre-built session for resumption)

Go

package main

import (
    "github.com/autohandai/agentsdk-go"
)

session := agentsdk.NewSession()
session.AddUserMessage("Analyze this code:")
session.AddAssistantMessage("Sure, I'll look at it now.")
session.AddUserMessage("Thanks, start with main.go")

// Now pass the session to the runner
// (Runner currently creates its own Session, but future
// versions will accept a pre-built session for resumption)

Swift

import AutohandAgents

var session = Session()
session.addUserMessage("Analyze this code:")
session.addAssistantMessage("Sure, I'll look at it now.")
session.addUserMessage("Thanks, start with main.swift")

// Now pass the session to the runner
// (Runner currently creates its own Session, but future
// versions will accept a pre-built session for resumption)

Rust

use autohand_agents::{Session, Message};

let mut session = Session::new();
session.add_user_message("Analyze this code:");
session.add_assistant_message("Sure, I'll look at it now.");
session.add_user_message("Thanks, start with main.rs");

// Now pass the session to the runner
// (Runner currently creates its own Session, but future
// versions will accept a pre-built session for resumption)

Use Cases

Resuming a Long Conversation

Save after each significant agent run. If your process crashes, you still have the message history.

TypeScript

const result = await Runner.run(agent, "Refactor the database migration");
await result.session.save("migration-session.json");

Python

result = Runner.run_sync(agent, "Refactor the database migration")
result.session.save("migration-session.json")

Java

RunResult result = Runner.runSync(agent, "Refactor the database migration");
result.getSession().save("migration-session.json");

Go

result := agentsdk.RunnerRunSync(agent, "Refactor the database migration")
result.Session.Save("migration-session.json")

Swift

let result = try await Runner.run(agent, prompt: "Refactor the database migration")
try result.session.save(to: URL(fileURLWithPath: "migration-session.json"))

Rust

let result = Runner::run_sync(&agent, "Refactor the database migration")?;
result.session.save("migration-session.json")?;

Building a Chat UI

Each message in a web UI maps to a session message.

TypeScript

for (const msg of session.messages) {
  if (msg.role === "user") {
    renderUserBubble(msg.content);
  } else if (msg.role === "assistant") {
    renderAssistantBubble(msg.content);
  }
}

Python

for msg in session.messages:
    if msg.role == "user":
        render_user_bubble(msg.content)
    elif msg.role == "assistant":
        render_assistant_bubble(msg.content)

Java

for (Message msg : session.getMessages()) {
    if (msg.getRole() == "user") {
        renderUserBubble(msg.getContent());
    } else if (msg.getRole() == "assistant") {
        renderAssistantBubble(msg.getContent());
    }
}

Go

for _, msg := range session.Messages {
    if msg.Role == "user" {
        renderUserBubble(msg.Content)
    } else if msg.Role == "assistant" {
        renderAssistantBubble(msg.Content)
    }
}

Swift

for msg in session.messages {
    if msg.role == "user" {
        renderUserBubble(msg.content)
    } else if msg.role == "assistant" {
        renderAssistantBubble(msg.content)
    }
}

Rust

for msg in &session.messages {
    if msg.role == "user" {
        render_user_bubble(&msg.content);
    } else if msg.role == "assistant" {
        render_assistant_bubble(&msg.content);
    }
}

Auditing Agent Behavior

The session's message history is your audit trail. You can log it, search it, and replay it to understand what the agent did and why.

TypeScript

import fs from 'fs/promises';

await session.save("audit-log.json");
// Later: audit-log.json is a complete record of every tool call,
// every response, every error. Useful for debugging and compliance.

Python

session.save("audit-log.json")
# Later: audit-log.json is a complete record of every tool call,
# every response, every error. Useful for debugging and compliance.

Java

session.save("audit-log.json");
// Later: audit-log.json is a complete record of every tool call,
// every response, every error. Useful for debugging and compliance.

Go

session.Save("audit-log.json")
// Later: audit-log.json is a complete record of every tool call,
// every response, every error. Useful for debugging and compliance.

Swift

try session.save(to: URL(fileURLWithPath: "audit-log.json"))
// Later: audit-log.json is a complete record of every tool call,
// every response, every error. Useful for debugging and compliance.

Rust

session.save("audit-log.json")?;
// Later: audit-log.json is a complete record of every tool call,
// every response, every error. Useful for debugging and compliance.