# 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

| Trait / Type | Purpose |
|---|---|
| `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 = "2"
```

```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

`2.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).
