Skip to main content

Crate kanshou

Crate kanshou 

Source
Expand description

kanshou (観照 — contemplation/introspection) — live process introspection over Unix sockets.

Every long-running pleme-io binary opens a kanshou socket at a canonical path on startup. Operators, MCP servers, and other processes query the live AppState over that socket: sessions in flight, atomic frame counters, queue depths, “what config did you actually load”, whatever the binary registers.

Closes the “I have an MCP but no wire into the live process” class: mado MCP returning process-local zeros while the GUI mado renders a hundred frames a second, tear MCP showing zero sessions while embedded tear sees one, “is tend processing X right now?” requiring pgrep + lsof archaeology.

§Three modules

  • types — wire schema (Query, QueryResult, Introspect trait). Stable serde shape.
  • serverServer<T>. Wrap an Arc<T: Introspect>, call .serve(), the socket appears at the canonical path and accepts queries.
  • clientClient + discover. Connect to a running binary by name+pid or auto-discover.

§Canonical socket path

  • macOS: $HOME/Library/Application Support/kanshou/<app>-<pid>.sock
  • linux: $XDG_RUNTIME_DIR/kanshou/<app>-<pid>.sock (falls back to /tmp/kanshou-<uid> if XDG_RUNTIME_DIR unset)

§Wire protocol

Length-prefixed JSON-RPC. Each frame is u32 BE length then JSON bytes. Request: serialized types::Query. Response: serialized types::QueryResult. Connection stays open across multiple request/response cycles; the server closes only on EOF or hard I/O error.

§Minimal consumer example

use std::sync::Arc;
use kanshou::{server::Server, types::{Introspect, Query, QueryResult, QueryError}};

struct AppState { sessions: Vec<String>, frame_count: u64 }

impl Introspect for AppState {
    fn query(&self, q: &Query) -> QueryResult {
        match q.path.as_slice() {
            [first] if first == "sessions" => Ok(serde_json::to_value(&self.sessions).unwrap()),
            [first] if first == "frame_count" => Ok(serde_json::to_value(self.frame_count).unwrap()),
            _ => Err(QueryError::unknown_field(q.path.join("."))),
        }
    }
    fn schema(&self) -> &'static [&'static str] {
        &["sessions", "frame_count"]
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let state = Arc::new(AppState { sessions: vec![], frame_count: 0 });
    let server = Server::new("myapp", state)?;
    server.serve().await?;
    Ok(())
}

§Theory

Phase 1 of the fleet-wide live-introspection wave (next phases: #[derive(Introspect)] in gen-macros, mado/tear retrofit, fleet rollout, gen kanshou operator CLI). The substrate gains one typed primitive: every running pleme-io process becomes queryable by construction.

Re-exports§

pub use client::discover;
pub use client::Client;
pub use client::DiscoveredInstance;
pub use server::Server;
pub use types::Introspect;
pub use types::Query;
pub use types::QueryError;
pub use types::QueryResult;

Modules§

client
Discovery + client. Walk the socket directory to enumerate every running kanshou consumer on this host; open a connection to one and ship queries through it.
mcp
MCP-forwarding helper. The canonical shape for a stdio MCP server that needs to introspect a separate GUI / daemon process: discover the live consumer over kanshou, forward the query, fall back to a local implementation when no consumer is running.
path
Canonical socket path resolution. Same algorithm consumed by the server (binds) and the client (discovers).
server
Unix-socket introspection server.
types
Typed wire schema — the contract every kanshou server speaks and every client consumes. Stable serde shape.

Derive Macros§

Introspect
Auto-implement kanshou::Introspect for a struct with named pub fields. See module docs for attribute reference.