# klieo-core
Core traits + runtime for the klieo agent framework.
Part of the [klieo](https://crates.io/crates/klieo) Rust agent framework.
## Newcomers: start with `klieo::App`
Direct `klieo-core` use means hand-wiring `Arc<dyn …>` ports for LLM,
memory, bus, tools, and runlog into an `AgentContext`. That's the
advanced wiring path. For laptop-dev and the common case reach for
the [`klieo`](https://crates.io/crates/klieo) umbrella crate:
```rust,no_run
# async fn run() -> anyhow::Result<()> {
let app = klieo::App::local().build().await?;
# let _ = app; Ok(()) }
```
`App::local()` wires LLM (Ollama), short/long/episodic memory
(in-memory + SQLite), pubsub (in-process), and a tool dispatcher with
sensible defaults. `klieo-core` is the trait surface those defaults
program against — pick this crate directly only when you're writing a
new provider, a custom port implementation, or a bespoke `AgentContext`.
## What it is
`klieo-core` defines the trait surface that all klieo crates program
against. Implement these traits once; swap implementations without
touching agent code.
## Trait surface
| `Agent` | Runnable agent with typed input/output |
| `SimpleAgent` | Built-in single-LLM agent implementation |
| `Tool` / `ToolInvoker` | Callable tool + dispatcher |
| `LlmClient` | LLM completion provider |
| `ShortTermMemory` | Ephemeral key-value memory |
| `LongTermMemory` | Persistent / vector memory |
| `EpisodicMemory` | Append-only episode log |
| `Pubsub` | Publish/subscribe bus |
| `RequestReply` | RPC over bus |
| `KvStore` | Distributed key-value store |
| `JobQueue` | Durable work queue |
| `ServerOutbound` | Server-initiated outbound requests (MCP/A2A) |
## Worked example — `SimpleAgent`
`SimpleAgent` is the trait-level entry point — built-in single-LLM
agent with a tool catalogue, ready to plug into a hand-wired
`AgentContext` or into `klieo::App` via `.agent(...)`.
```toml
[dependencies]
klieo-core = "3"
```
```rust
use klieo_core::llm::ToolDef;
use klieo_core::{Agent, SimpleAgent};
let agent = SimpleAgent::new(
"weather-bot",
"Answer weather questions. Use the get_forecast tool when asked.",
vec![ToolDef::new(
"get_forecast",
"Get the forecast for a city.",
serde_json::json!({
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"],
}),
)],
);
assert_eq!(agent.name(), "weather-bot");
assert_eq!(agent.tools().len(), 1);
```
`SimpleAgent::new(name, system_prompt, catalogue)` returns an `Agent`.
Running it needs an `AgentContext` — open `klieo::App::builder()` to
get one wired up; or hand-wire `AgentContextBuilder` directly when you
own every port.
## Implementing a custom `Tool`
For one-off tools, reach for the [`#[tool]`](https://crates.io/crates/klieo-macros)
macro from `klieo-macros` — generates `Tool` + JSON schema from an
`async fn`. For tools that don't fit the macro shape (custom schema,
hand-tuned error mapping, stateful tools) implement the trait directly:
```rust
use klieo_core::tool::{Tool, ToolCtx};
use klieo_core::error::ToolError;
use async_trait::async_trait;
struct MyTool;
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str { "my_tool" }
fn description(&self) -> &str { "Does something useful." }
fn json_schema(&self) -> &serde_json::Value {
static SCHEMA: std::sync::OnceLock<serde_json::Value> = std::sync::OnceLock::new();
SCHEMA.get_or_init(|| serde_json::json!({}))
}
async fn invoke(
&self,
_args: serde_json::Value,
_ctx: ToolCtx,
) -> Result<serde_json::Value, ToolError> {
Ok(serde_json::json!({ "result": "ok" }))
}
}
```
## Status
`3.x` — stable. See [`docs/SEMVER.md`](https://github.com/mohrimic/klieo/blob/main/docs/SEMVER.md).
## License
MIT — see [`LICENSE`](https://github.com/mohrimic/klieo/blob/main/LICENSE).