Work with sessions
Every agent interaction is a session — a sequence of messages exchanged between the user, the LLM, and tool results. Sessions let you inspect, resume, and persist conversations, while the TypeScript SDK can also detect concurrent local agents in the same workspace.
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 ofMessageobjects in chronological orderworking_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();
}
| Tier | Behavior | Use 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.