Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

agentd

A single-user local daemon that runs multi-agent task DAGs with persistent, hybrid memory. Apps are thin clients speaking newline-delimited JSON (NDJSON) over a local IPC socket — Unix domain socket on Unix, Windows named pipe on Windows, or TCP when forced.

  • One daemon owns all state: sessions, task graphs, memory, worker pool.
  • A coordinator schedules DAG nodes onto a shared worker pool with per-session quotas; each worker runs a minimal agent turn loop (provider -> tool calls -> report).
  • Hybrid memory: structured facts plus cosine vector recall via a pluggable embedding backend.
  • State persists to disk; in-progress graphs auto-resume on restart.

Quick start

cargo run

The daemon binds its platform default endpoint — a Unix socket (<data-dir>/agentd.sock) on Unix, a named pipe on Windows — and writes it to <data-dir>/endpoint so clients can discover it. Override the data dir with --data-dir or AGENTD_DATA_DIR, or force a specific transport with --endpoint / AGENTD_ENDPOINT:

cargo run -- --endpoint unix:///tmp/agentd.sock   # force UDS
AGENTD_ENDPOINT=tcp://127.0.0.1:0 cargo run       # force TCP (any platform)

The daemon logs agentd listening on <endpoint> when RUST_LOG=info (or AGENTD_VERBOSE) is set.

There is no in-place reload — the model is stop-and-restart. Stop the daemon gracefully by sending a Shutdown request (the server replies ShutdownAck and exits its accept loop), by Ctrl-C, or on Unix by SIGTERM/SIGINT. The daemon does not unlink its socket on shutdown (matching the lock file's never-unlink-while-held rule); a stale socket file from a crash or graceful stop is removed by the NEXT start, under the single-instance lock, before binding. Windows pipes leave no filesystem artifact either way. Persisted sessions/graphs/memory resume automatically on restart.

Without a config.toml, the daemon still starts, but provider calls fail fast (no api_key). To actually run agents, drop a config.toml into the data dir first — see below.

Configuration

config.toml lives in the data dir (~/.agentd/config.toml by default):

[provider]
base_url = "https://api.openai.com/v1"
api_key = "sk-..."
model = "gpt-4o-mini"

# Optional: embedding overrides. Defaults to the provider endpoint's
# /embeddings route with the same model.
[embedding]
base_url = "https://api.openai.com/v1"
model = "text-embedding-3-small"

max_concurrency = 4      # worker pool size
per_session_quota = 2    # max nodes running concurrently per session

Everything is optional except a working provider.api_key for real runs.

Protocol

Transport is a local IPC socket — Unix domain socket on Unix, Windows named pipe on Windows, or TCP when forced via --endpoint/AGENTD_ENDPOINT. All backends carry the same frames: one JSON object per line, always carrying "v": 1 and a numeric id that correlates request and reply. Requests that don't match a known kind decode as Unknown and get an Unsupported error — never a parse error.

Requests -> replies

Request Fields Reply
ping pong
create_session name, project session_created / invalid_request
list_sessions session_list
open_session session_id session_opened / not_found
submit_task_graph session_id, tasks[] graph_accepted / invalid_graph
graph_status graph_id graph_status / not_found
cancel_graph graph_id cancel_ack / not_found
cancel_task graph_id, task_id cancel_ack / not_found
send_message session_id, worker_id, text send_message_ack / not_found
register_tool def tool_registered
tool_call_result call_id, output tool_call_result_ack
memory_store session_id, entry memory_stored / invalid_request
memory_query session_id, text, limit memory_results / invalid_request
stats stats
shutdown shutdown_ack, then the daemon stops accepting

Events (pushed to the owning connection)

Kind Payload Meaning
graph_updated graph_id, node_deltas[] node state transitions
worker_turn session_id, worker_id, text_delta agent text output
tool_call_request call_id, tool, input app must run a registered tool
memory_injected session_id, memory_ids[] memory surfaced to a worker

Error codes

not_found, invalid_graph, task_failed, tool_error, provider_error, unsupported, busy, invalid_request.

Client usage

use agentd_client::AgentdClient;
use agentd_core::Task;
use agentd_protocol::{Event, ReplyKind, RequestKind};

fn main() -> anyhow::Result<()> {
    let mut client = AgentdClient::connect_default()?; // resolves <data-dir>/endpoint

    client.request(RequestKind::Ping)?; // -> Pong

    let session_id = match client.request(RequestKind::CreateSession {
        name: "app".into(),
        project: "proj".into(),
    })? {
        ReplyKind::SessionCreated { meta } => meta.id,
        _ => return Err(anyhow::anyhow!("create failed")),
    };

    let graph_id = match client.request(RequestKind::SubmitTaskGraph {
        session_id,
        tasks: vec![Task::new("t1", "summarize the docs", vec![], 2)],
    })? {
        ReplyKind::GraphAccepted { graph_id } => graph_id,
        _ => return Err(anyhow::anyhow!("submit failed")),
    };

    let mut events = client.events();
    loop {
        match events.recv() {
            Ok(Event::GraphUpdated { node_deltas, .. }) => println!("nodes: {node_deltas:?}"),
            Ok(Event::ToolCallRequest { tool, input, .. }) => {
                // run the tool in your app, then:
                client.request(RequestKind::ToolCallResult {
                    call_id,
                    output: tool_output,
                })?;
            }
            Ok(Event::WorkerTurn { text_delta, .. }) => println!("agent: {text_delta}"),
            Ok(_) => {}
            Err(_) => break, // daemon closed the connection
        }
    }
    let _ = graph_id;
    Ok(())
}

See crates/agentd-client/tests/e2e.rs for a complete round-trip.

Architecture

+------------+   NDJSON (UDS / pipe / TCP)  +-------------------------------------------+
|   App      |  --------------------------> |  agentd daemon                            |
| (client)   |  <-------------------------- |                                           |
+------------+                               |  server.rs        accept loop, conns      |
                                            |  state.rs          shared DaemonState      |
                                            |  coordinator.rs    DAG scheduler + quotas  |
                                            |  worker pool       agent turn loops        |
                                            |    agent.rs        provider <-> tools      |
                                            |    provider.rs     LLM + streaming         |
                                            |    tool_callbacks callback routing        |
                                            |    memory.rs       facts + vector recall   |
                                            |    embedding.rs    pluggable embedder      |
                                            |    persistence.rs  atomic disk writes      |
                                            +-------------------------------------------+
                                                        |
                                                ~/.agentd/  (lock, endpoint, sessions.json,
                                                            graphs/, memory/, config.toml)
  • agentd-core — data contracts (DTOs only, no I/O).
  • agentd-protocol — request/reply/event codecs.
  • agentd-transportEndpoint/Listener/Stream/ClientStream abstraction over UDS, Windows named pipes, and TCP.
  • agentd-daemon — the server.
  • agentd-client — blocking client crate for apps.

Testing

cargo test --workspace          # full suite (all crates)
cargo test -p agentd-core       # graph model, memory types
cargo test -p agentd-protocol   # codecs
cargo test -p agentd-daemon     # server, coordinator, memory, lock
cargo test -p agentd-client     # e2e + restart + concurrency over a real socket

cargo clippy --all-targets      # lints
cargo fmt                       # formatting

Tests never touch a real provider: integration tests inject MockProvider and MockEmbedding from agentd-daemon::testutil.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages