Complete reference for the Honcho Rust SDK. Every public type, method, and pattern.
- Client (
Honcho) - Peer
- Session
- Message
- Conclusion
- DialecticStream
- Error Handling
- Pagination
- File Upload (
FileSource) - Blocking API
The entry point for all Honcho operations. Wraps an HTTP client, workspace scope, and lazy workspace initialization.
// Quick constructor
let client = Honcho::new("http://localhost:8000", "my-workspace")?;
// Builder with full control
let client = Honcho::from_params(
Honcho::builder()
.api_key("sk-...") // or HONCHO_API_KEY env
.base_url("http://localhost:8000") // or HONCHO_URL env
.workspace_id("my-workspace") // or HONCHO_WORKSPACE_ID env
.timeout(Duration::from_secs(120))
.max_retries(3)
.default_headers(headers)
.default_query(query_params)
.build(),
)?;
// Using Environment enum
let client = Honcho::from_params(
Honcho::builder()
.environment(Environment::Production) // https://api.honcho.dev
.build(),
)?;Resolution order: explicit builder arg → HONCHO_* env var → default.
Explicitly ensure the workspace exists on the server. Normally workspace creation is lazy (triggered by the first peer/session call). Use this to pre-create workspaces or catch misconfigurations early.
client.force_ensure().await?;Returns the workspace ID this client is scoped to.
Returns the resolved (normalized) base URL.
Get-or-create a peer. Will also ensure the workspace if not done yet.
let peer = client.peer("alice", None, None).await?;
let peer = client.peer("alice", Some(metadata), Some(config)).await?;Get-or-create a session.
use honcho_ai::session::PeerSpec;
let session = client.session("chat-1", None, None, None).await?;
let session = client.session("chat-1", None, Some(vec![
PeerSpec::Id("alice".into()),
PeerSpec::WithConfig("bob".into(), config),
]), None).await?;Refresh workspace state by re-fetching metadata and configuration.
// Metadata (raw HashMap)
let meta = client.get_metadata().await?;
client.set_metadata(meta).await?;
// Typed configuration
let config = client.get_configuration().await?;
client.set_configuration(&config).await?;
// Raw JSON access (for future/unknown fields)
let raw_config = client.get_configuration_raw().await?;
client.set_configuration_raw(raw_config).await?;// Peers — page 1, 50 items
let page = client.peers().await?;
let page = client.peers_with_filters(filters, page, size, reverse).await?;
// Sessions
let page = client.sessions().await?;
let page = client.sessions_with_filters(filters, page, size, reverse).await?;
// Workspaces (IDs only)
let page = client.workspaces().await?;Search messages across the workspace (semantic search).
let results = client.search("important topic", None, None).await?;
let results = client.search("topic", Some(5), Some(filters)).await?;Get queue processing status.
let status = client.queue_status(None, None, None).await?;Schedule a background memory consolidation task.
client.schedule_dream("alice", None, None).await?;
client.schedule_dream("alice", Some("chat-1"), Some("bob")).await?;Delete a workspace by ID.
client.delete_workspace("old-workspace").await?;Represents a peer (identity) in a Honcho workspace. Provides chat (dialectic reasoning), representation, context, conclusions, search, and metadata management.
Always created via client.peer() — never constructed directly.
let id = peer.id(); // &str
let meta = peer.metadata(); // Option<HashMap<String, Value>>
let config = peer.configuration(); // Option<PeerConfig>// Simple
let reply: Option<String> = peer.chat("What does Alice like?").await?;
// With full options (session, target, reasoning level)
use honcho_ai::types::dialectic::DialecticOptions;
let opts = DialecticOptions::builder()
.query("What did Bob say about the project?")
.target("bob")
.session_id("chat-1")
.reasoning_level(honcho_ai::types::dialectic::ReasoningLevel::Medium)
.build();
let reply = peer.chat_with_options(&opts).await?;Returns Ok(None) when the server response has no content.
use futures_util::StreamExt;
// Simple streaming
let mut stream = peer.chat_stream("Hello").send().await?;
while let Some(chunk) = stream.next().await {
print!("{}", chunk?);
}
// Full builder
let mut stream = peer.chat_stream("What does Bob think?")
.target("bob")
.session_id("chat-1")
.reasoning_level(ReasoningLevel::High)
.send()
.await?;
// Final response after stream completes
let final_resp = stream.final_response();
println!("Full response: {}", final_resp.content());The peer's self-model — what it "knows" about itself.
// Default parameters
let rep = peer.representation().await?;
// With search context
let rep = peer.representation_builder()
.search_query("hobbies")
.search_top_k(10)
.send()
.await?;Generate context blocks consumable by OpenAI and Anthropic message APIs.
// Default context
let ctx = peer.context().await?;
// Context from the perspective of another peer
let ctx = peer.context_with_target("bob").await?;
// Full builder with options
let ctx = peer.context_builder()
.search_query("project alpha")
.search_top_k(20)
.target("bob") // get context about bob
.send()
.await?;
// Access formatted context for direct LLM use
println!("{}", ctx.openai); // OpenAI format
println!("{}", ctx.anthropic); // Anthropic format
println!("{}", ctx.session_context); // session-level contextA curated list of key facts about a peer.
let card = peer.get_card().await?; // Vec<String>
peer.set_card(vec!["friendly".into(), "rustacean".into()]).await?;Durable facts about peers, formed through the dialectic process.
// Conclusions about this peer
let scope = peer.conclusions();
let page = scope.list().send().await?;
let created = scope.create(["Likes hiking"]).await?;
scope.delete(conclusion_id).await?;
// Conclusions from this peer about another peer
let cross = peer.conclusions_of("bob");
let page = cross.list().send().await?;let results = peer.search("topic").await?;
let results = peer.search_with_options(&opts).await?;
let sessions_page = peer.sessions().await?;peer.refresh().await?;
let meta = peer.get_metadata().await?;
peer.set_metadata(meta).await?;
peer.update(patch).await?; // patch update — only changes sent fields
let config = peer.get_configuration().await?;
peer.set_configuration(&config).await?;
let raw = peer.get_configuration_raw().await?;
peer.set_configuration_raw(raw).await?;Represents a conversation session grouping messages between peers.
Always created via client.session().
let id = session.id(); // &str
let active = session.is_active(); // bool
let meta = session.metadata(); // Option<HashMap<String, Value>>
let config = session.configuration(); // Option<SessionConfiguration>
let created = session.created_at(); // &DateTime<Utc>Messages are created via Peer::message():
let msg = peer.message("Hello world!").build()?;
let msg = peer.message("Hello")
.metadata(json!({"source": "api"}))
.configuration(json!({"reasoning": true}))
.build()?;// Add messages (1-100 at a time)
let msgs: Vec<Message> = session.add_messages(vec![msg1, msg2]).await?;
// Get single message
let msg = session.get_message("msg-id").await?;
// Update message metadata
let msg = session.update_message("msg-id", new_metadata).await?;let page = session.messages().await?; // page 1, 50 items
let page = session.messages_with_options(Some(filters), page, size, reverse).await?;The messages_with_options method allows paginated listing with filters:
filters:Option<HashMap<String, Value>>page:u64(1-based)size:u64(1..=100)reverse:bool
Rust-exclusive feature: typed file sources.
use honcho_ai::FileSource;
// From bytes (in-memory)
let msgs = session.upload_file(
FileSource::bytes("doc.pdf", &pdf_data, "application/pdf")
).peer("alice").send().await?;
// From file path (streams from disk, re-opens on retry)
let msgs = session.upload_file(
FileSource::path("report.txt")
).peer("alice")
.metadata(json!({"source": "filesystem"}))
.send()
.await?;
// From async stream (buffered once, then replayed on retry)
let msgs = session.upload_file(
FileSource::stream("large.bin", reader, "application/octet-stream")
).peer("alice").send().await?;The upload builder chain requires .peer(id) before .send(). Optional: .metadata(), .configuration(), .created_at().
MIME types: text/*, application/pdf, application/json, image/*, audio/*, video/*.
session.add_peer("bob").await?;
session.add_peers([("alice", config), ("bob", config)]).await?;
session.set_peers(["alice", "bob"]).await?; // replaces all
session.remove_peers(["bob"]).await?;
let peers = session.peers().await?; // Vec<PeerResponse>Per-peer configuration:
let cfg = session.get_peer_configuration("alice").await?;
session.set_peer_configuration("alice", &cfg).await?;Note:
set_peer_configurationrequires the peer to already be in the session (viaadd_peer/set_peersfirst).
// Session context (for LLM consumption)
let ctx = session.context().await?;
let ctx = session.context_with_options(&opts).await?;
// Or using context_builder
let ctx = session.context_builder()
.summary(true)
.peer_target("alice")
.peer_perspective("bob")
.send()
.await?;
println!("{}", ctx.openai);
println!("{}", ctx.anthropic);
// Search within session
let results = session.search("topic").await?;
let results = session.search_with_options(&opts).await?;
// Session summaries
let summaries = session.summaries().await?;
// Peer representation within session context
let rep = session.representation("alice").await?;
// Queue status
let status = session.queue_status(None, None).await?;let cloned = session.clone_session().await?;
let cloned = session.clone_session_with_message("msg-42").await?; // clone from message point
session.delete().await?;session.refresh().await?;
let meta = session.get_metadata().await?;
session.set_metadata(meta).await?;
let config = session.get_configuration().await?;
session.set_configuration(config).await?;A message in a Honcho session. Created via Peer::message().
All fields accessed through methods (not direct field access):
let id = msg.id(); // &str
let content = msg.content(); // &str
let peer_id = msg.peer_id(); // &str
let session_id = msg.session_id();// &str
let workspace_id = msg.workspace_id(); // &str
let metadata = msg.metadata(); // &HashMap<String, Value>
let created_at = msg.created_at();// &DateTime<Utc>
let token_count = msg.token_count(); // u64let msg: MessageCreate = peer.message("Hello from Rust!")
.metadata(json!({"timestamp": "2026-01-01T00:00:00Z"}))
.configuration(json!({"reasoning": true}))
.build()?;Debug— truncated to 50 chars, multibyte-safeDisplay— full content textSend + Sync + Clone
A durable fact about a peer. Managed through Peer::conclusions() and Peer::conclusions_of().
let scope = peer.conclusions(); // ConclusionScope
// List conclusions
let page = scope.list().send().await?;
for conclusion in page.items() {
println!("{}: {}", conclusion.id(), conclusion.content());
}
// Create
use honcho_ai::ConclusionCreateParams;
let created = scope.create(["Likes hiking in mountains"]).await?;
// Create with optional session association
let params = ConclusionCreateParams::builder()
.content("Dislikes broccoli")
.session_id("chat-1")
.build();
let created = scope.create([params]).await?;
// Delete
scope.delete("conclusion-id").await?;
// Cross-peer conclusions
let cross = peer.conclusions_of("bob");
let page = cross.list().send().await?; // what this peer concludes about bobSSE stream adapter for streaming chat responses.
use futures_util::StreamExt;
let mut stream = peer.chat_stream("Hello").send().await?;
// Stream individual chunks
while let Some(chunk) = stream.next().await {
match chunk {
Ok(text) => print!("{text}"),
Err(e) => eprintln!("Stream error: {e}"),
}
}
// Access final response after stream completes
let final_response = stream.final_response();
println!("Content: {}", final_response.content());
// Check completion status without consuming
if !stream.is_complete() {
println!("Stream still active...");
}let resp = stream.final_response();
let content = resp.content(); // &str — the full response text
let raw = resp.raw(); // &Value — raw JSON if neededAll API methods return Result<T, HonchoError> where HonchoError is #[non_exhaustive].
Use code() for stable pattern matching:
match e.code() {
"not_found" => /* handle missing resource */,
"authentication_error" => /* bad or missing API key */,
"rate_limit_exceeded" => /* back off */,
"validation_error" => /* invalid input */,
"timeout" | "connection_error" => /* retry */,
_ => /* unknown or server error */,
}| Code | HTTP | Description |
|---|---|---|
bad_request |
400 | Invalid request |
authentication_error |
401 | Missing/invalid API key |
permission_denied |
403 | Insufficient permissions |
not_found |
404 | Resource not found |
conflict |
409 | Resource already exists |
unprocessable_entity |
422 | Semantic validation failure |
rate_limit_exceeded |
429 | Too many requests |
client_error |
4xx | Other client error |
server_error |
5xx | Server error |
timeout |
— | Request timed out |
connection_error |
— | Network failure |
transport_error |
— | Reqwest transport error |
decode_error |
— | Response parsing failure |
io_error |
— | Filesystem I/O error |
configuration_error |
— | Client configuration issue |
validation_error |
— | Client-side validation |
e.code() // "not_found"
e.status_code() // Some(404) or None for non-HTTP errors
e.message() // human-readable message
e.is_retryable() // true for timeout, connection, 429, 5xx
e.retry_after() // Some(Duration) for 429 with Retry-After headerThe HTTP client automatically retries retryable errors: up to 2 retries, 500ms initial delay with exponential backoff. Respects Retry-After header. You typically don't need your own retry logic.
All list methods return Page<T>.
let page: Page<Peer> = client.peers().await?;
page.items(); // &[Peer] — current page items
page.page(); // u64 — current page number (1-based)
page.size(); // u64 — items per page
page.total(); // u64 — total items across all pages
page.pages(); // u64 — total pages
page.has_next(); // booluse futures_util::StreamExt;
let stream = page.into_stream();
tokio::pin!(stream);
while let Some(Ok(item)) = stream.next().await {
// process each item lazily — fetches pages as needed
}let mut page = client.peers_with_filters(filters, 1, 10, false).await?;
loop {
for peer in page.items() {
println!("{}", peer.id);
}
if !page.has_next() { break; }
page = page.next_page().await?; // Result<Option<Page<T>>>
}Filters are HashMap<String, Value> — the same format as Python/TS SDKs:
let mut filters = HashMap::new();
filters.insert("is_active".into(), json!(true));
filters.insert("role".into(), json!("admin"));
let page = client.peers_with_filters(filters, 1, 10, false).await?;Validation: page >= 1, size in 1..=100, validated client-side before making the request.
Rust-exclusive typed abstraction for file sources.
pub enum FileSource {
/// Upload from in-memory bytes.
Bytes { filename: String, bytes: Vec<u8>, content_type: String },
/// Stream from disk — re-opens on retry, unbounded file size.
Path(std::path::PathBuf),
/// From an async reader — buffered once, replayed on retry.
Stream { filename: String, reader: Box<dyn AsyncRead + Send + Unpin>, content_type: String },
}FileSource::bytes("file.txt", b"hello", "text/plain")
FileSource::path("report.pdf")
FileSource::stream("data.bin", async_reader, "application/octet-stream")session.upload_file(source)
.peer("alice") // required
.metadata(json!({"note": "important"})) // optional
.configuration(json!({"process": true}))// optional
.created_at(Utc::now()) // optional
.send()
.await?; // returns Vec<Message>Enable with features = ["blocking"]:
honcho-ai = { version = "0.1", features = ["blocking"] }use honcho_ai::blocking::Honcho;
let client = Honcho::new("http://localhost:8000", "my-workspace")?;
let peer = client.peer("alice")?;
let reply = peer.chat("Hello")?;
let session = client.session("chat-1")?;
session.add_messages(vec![peer.message("Hi").build()?])?;All types are available either via honcho_ai (async) or honcho_ai::blocking (sync):
| Async | Blocking |
|---|---|
honcho_ai::Honcho |
honcho_ai::blocking::Honcho |
honcho_ai::Peer |
honcho_ai::blocking::Peer |
honcho_ai::Session |
honcho_ai::blocking::Session |
honcho_ai::Message |
honcho_ai::blocking::Message |
Error types are shared — honcho_ai::error::HonchoError works with both.
The blocking API panics if called from within an existing async runtime (e.g., inside tokio::spawn or #[tokio::test]). Use the async client in those cases.
Many types use bon::Builder with #[builder(finish_fn = build)]:
let params = Honcho::builder()
.base_url("...")
.workspace_id("...")
.build();
let opts = DialecticOptions::builder()
.query("...")
.session_id("...")
.build();
let ctx_opts = SessionContextOptions::builder()
.peer_perspective("alice")
.peer_target("bob")
.build();
ctx_opts.validate()?; // required when perspective/target is set