sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
//! Example: two `JsonlBackend`s side by side, demonstrating boot-from-config,
|
||||
//! priority-ordered read routing, write fanout, and a health snapshot.
|
||||
//!
|
||||
//! Layout:
|
||||
//! - `primary` → `read_priority = 0`, `failure_mode = required` (default)
|
||||
//! - `mirror` → `read_priority = 10`, `failure_mode = best_effort`
|
||||
//!
|
||||
//! The primary is the default write target (lowest priority among
|
||||
//! Required+write-active backends). `append_messages` fans out inline to the
|
||||
//! mirror too. Reads walk the registrations in priority order, so the primary
|
||||
//! answers first; if it is missing a session, the walk falls through to the
|
||||
//! mirror.
|
||||
//!
|
||||
//! Run with:
|
||||
//!
|
||||
//! cargo run --package dirigent_archivist --example multi_backend
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use dirigent_archivist::coordinator::Archivist;
|
||||
use dirigent_archivist::registry::{ArchivesConfig, BackendRegistry};
|
||||
use dirigent_archivist::types::{
|
||||
MessageRecord, RegisterConnectorRequest, RegisterSessionRequest,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let dir_a = tempfile::tempdir()?;
|
||||
let dir_b = tempfile::tempdir()?;
|
||||
|
||||
// Build a two-archive config entirely from TOML so the example doubles as
|
||||
// a faithful demonstration of the config surface.
|
||||
let cfg_src = format!(
|
||||
r#"
|
||||
[[archives]]
|
||||
name = "primary"
|
||||
type = "jsonl"
|
||||
read_priority = 0
|
||||
[archives.params]
|
||||
path = "{a}"
|
||||
|
||||
[[archives]]
|
||||
name = "mirror"
|
||||
type = "jsonl"
|
||||
failure_mode = "best_effort"
|
||||
read_priority = 10
|
||||
[archives.params]
|
||||
path = "{b}"
|
||||
"#,
|
||||
a = dir_a.path().to_string_lossy().replace('\\', "/"),
|
||||
b = dir_b.path().to_string_lossy().replace('\\', "/"),
|
||||
);
|
||||
let cfg: ArchivesConfig = toml::from_str(&cfg_src)?;
|
||||
let registry = BackendRegistry::with_jsonl();
|
||||
let archivist = Arc::new(Archivist::from_config(cfg, ®istry, None).await?);
|
||||
|
||||
println!("\n=== Multi-backend Archivist example ===\n");
|
||||
println!("Boot complete. Archives (ordered by read_priority):");
|
||||
for s in archivist.list_archives_with_health().await {
|
||||
println!(
|
||||
" - name={:<8} type={:<6} priority={:<3} enabled={} write_active={} failure_mode={:?} health={:?}",
|
||||
s.name,
|
||||
s.type_name,
|
||||
s.read_priority,
|
||||
s.enabled,
|
||||
s.write_active,
|
||||
s.failure_mode,
|
||||
s.health,
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Register a connector. The primary owns the canonical record; fanout
|
||||
// mirrors it to the secondary.
|
||||
// ------------------------------------------------------------------
|
||||
let connector_resp = archivist
|
||||
.register_connector(
|
||||
RegisterConnectorRequest {
|
||||
r#type: "Example".into(),
|
||||
title: "multi-backend demo".into(),
|
||||
client_native_id: "example://multi_backend".into(),
|
||||
custom_uid: None,
|
||||
metadata: serde_json::json!({ "demo": true }),
|
||||
fingerprint: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let connector_uid = connector_resp.connector_uid;
|
||||
println!(
|
||||
"\nRegistered connector: uid={} status={:?}",
|
||||
connector_uid, connector_resp.status
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Register a session under that connector. `register_session` writes
|
||||
// the mapping and `session.json` on the primary first, then fans out
|
||||
// to any enabled secondaries.
|
||||
// ------------------------------------------------------------------
|
||||
let session_resp = archivist
|
||||
.register_session(
|
||||
RegisterSessionRequest {
|
||||
connector_uid,
|
||||
native_session_id: "demo-session-1".into(),
|
||||
title: Some("multi-backend demo session".into()),
|
||||
custom_scroll_id: None,
|
||||
metadata: serde_json::json!({ "model": "demo" }),
|
||||
completeness: Default::default(),
|
||||
parent_scroll_id: None,
|
||||
is_subagent: false,
|
||||
continuation: None,
|
||||
agent_id: None,
|
||||
subagent_type: None,
|
||||
spawning_tool_use_id: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let scroll_id = session_resp.scroll_id;
|
||||
println!(
|
||||
"Registered session: scroll_id={} status={:?}",
|
||||
scroll_id, session_resp.status
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Append a couple of messages. `append_messages` writes to the primary
|
||||
// and then fans out inline to the mirror.
|
||||
// ------------------------------------------------------------------
|
||||
let user_msg = MessageRecord {
|
||||
version: 1,
|
||||
message_id: Uuid::now_v7(),
|
||||
session: scroll_id,
|
||||
parent_id: None,
|
||||
ts: Utc::now(),
|
||||
role: "user".into(),
|
||||
author: Some("alice".into()),
|
||||
content_md: "Hello from the multi-backend example!".into(),
|
||||
content_parts: None,
|
||||
attachments: vec![],
|
||||
metadata: serde_json::json!({}),
|
||||
};
|
||||
let asst_msg = MessageRecord {
|
||||
version: 1,
|
||||
message_id: Uuid::now_v7(),
|
||||
session: scroll_id,
|
||||
parent_id: Some(user_msg.message_id),
|
||||
ts: Utc::now(),
|
||||
role: "assistant".into(),
|
||||
author: Some("claude".into()),
|
||||
content_md: "Greetings. I have been written to two archives.".into(),
|
||||
content_parts: None,
|
||||
attachments: vec![],
|
||||
metadata: serde_json::json!({}),
|
||||
};
|
||||
archivist
|
||||
.append_messages(scroll_id, vec![user_msg, asst_msg], None)
|
||||
.await?;
|
||||
println!("\nAppended 2 messages — fanned out to primary + mirror.");
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Read path: the priority walk tries the primary first (priority=0).
|
||||
// It finds the session there and never consults the mirror.
|
||||
// ------------------------------------------------------------------
|
||||
let meta = archivist.get_session_metadata(scroll_id, None).await?;
|
||||
println!(
|
||||
"\nRead session via priority walk: title={:?} completeness={:?}",
|
||||
meta.title, meta.completeness
|
||||
);
|
||||
println!(
|
||||
"Read cache size after read: {}",
|
||||
archivist.read_cache_size().await
|
||||
);
|
||||
|
||||
let messages = archivist.get_messages(scroll_id, None).await?;
|
||||
println!("Read {} message(s) from the archive:", messages.len());
|
||||
for m in &messages {
|
||||
println!(" - [{}] {}", m.role, m.content_md);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Final health snapshot. Both backends should still be Available and
|
||||
// have no queued writes (both run Inline write policies by default).
|
||||
// ------------------------------------------------------------------
|
||||
println!("\nFinal health snapshot:");
|
||||
for s in archivist.list_archives_with_health().await {
|
||||
println!(
|
||||
" - {:<8} health={:?} queue_depth={:?} last_error={:?}",
|
||||
s.name, s.health, s.queue_depth, s.last_error
|
||||
);
|
||||
}
|
||||
|
||||
// Clean shutdown drains any queued writer tasks. Both backends here run
|
||||
// Inline, so this is effectively a no-op but remains the correct API.
|
||||
archivist.shutdown().await?;
|
||||
println!("\nShutdown complete.");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user