{
  "markdown": "# agentyk\n\n[![Crates.io](https://img.shields.io/crates/v/agentyk.svg)](https://crates.io/crates/agentyk)\n[![Documentation](https://docs.rs/agentyk/badge.svg)](https://docs.rs/agentyk)\n[![License](https://img.shields.io/crates/l/agentyk.svg)](LICENSE)\n\nCompose agents from values and run them.\n\n`agentyk` is a Rust library for building agents from a system prompt, model,\ntools, and capabilities. There is no entity lifecycle: nothing to create in a\nstore, nothing to register, and no ids to thread through your application.\nSessions produce a typed, replayable event log that can be persisted, resumed,\ninspected, and forked.\n\n- [API documentation](https://docs.rs/agentyk)\n- [Crate on crates.io](https://crates.io/crates/agentyk)\n- [Examples](crates/agentyk/examples)\n\n## Quick start\n\nAdd the facade crate; its default feature set stays offline and lightweight:\n\n```sh\ncargo add agentyk\n```\n\nBuild and run an agent by value. `SimDriver` makes this example deterministic\nand requires no API key or network access:\n\n```rust\nuse agentyk::{Agent, ModelSpec, Provider, SimDriver, SimTurn};\n\n# async fn example() -> agentyk::Result<()> {\nlet agent = Agent::builder()\n    .name(\"hello\")\n    .system_prompt(\"You are terse.\")\n    .model(ModelSpec::llmsim())\n    .provider(Provider::llmsim(SimDriver::new([SimTurn::text(\"Hello from agentyk.\")])))\n    .build()?;\n\nlet mut session = agent.session();\nlet turn = session.run(\"say hello\").await?;\nassert_eq!(turn.response, \"Hello from agentyk.\");\n# Ok(())\n# }\n```\n\nFor provider-backed agents, enable `http`; add `fs`, `mcp`, or `hooks` only\nwhen needed:\n\n```toml\n[dependencies]\nagentyk = { version = \"0.1\", features = [\"http\", \"fs\"] }\n```\n\nRun the complete offline example from a source checkout:\n\n```sh\ncargo run -p agentyk --example hello\n```\n\nOrdinary async functions can be attached directly as typed tools. The macro\nuses the function name as the tool name, derives its JSON schema from the\nparameters, and turns bad model arguments into a tool error:\n\n```rust\nuse agentyk::{Agent, ToolOutput};\n\n#[agentyk::tool(description = \"Add two integers.\")]\nasync fn add(a: i64, b: i64) -> ToolOutput {\n    ToolOutput::text((a + b).to_string())\n}\n\nlet agent = Agent::builder()\n    // model + provider...\n    .tool(add)\n    .build()?;\n```\n\nAdd a final `&ToolContext` parameter when the function needs session-scoped\nextensions, cancellation, or progress reporting; it is not exposed to the\nmodel. Custom parameter types derive `serde::Deserialize` and\n`agentyk::JsonSchema`. Capabilities remain the object-level bundle for prompt\ncontributions, dynamic tool discovery, commands, metadata, and shared state.\n\n## What's inside\n\n- **Turn loop** — the everruns `input → reason → act` contract: model\n  completion, tool execution, repeat until a text answer.\n- **Event log** — every step is a typed event (`turn.started`,\n  `input.message`, `tool.completed`, …). Logs are pluggable\n  (`InMemoryEventLog`, the single-process local `JsonlEventLog`, or your own\n  production `EventStore` impl) and sessions resume by replaying them. A host\n  store owns cross-process concurrency, fsync/transaction durability, access\n  control, tail recovery, and physical branch layout. Bounded pages,\n  immutable historical points, forks, and disposable snapshots support\n  long-lived timelines.\n- **Event listeners** — `EventListener` observes durable and ephemeral events,\n  optionally filtered by event type. `CompositeEventListener` combines\n  observers with ordered, panic-isolated delivery.\n- **Capabilities** — composable extensions contributing system-prompt text and\n  tools, attached by object:\n  `.capability(FileSystemCapability::new(store))`. The bundled filesystem one\n  covers what a coding agent needs: read (whole file or a line window),\n  write, targeted `edit_file`, `grep_files`, `stat_file`, list, delete.\n- **Cancellation that lands** — a `CancellationToken` stops the turn *and*\n  drops the tool call in flight, so cancelling during a long build or test run\n  takes effect immediately instead of when the command happens to finish.\n- **Model profiles** — attach a `ModelCatalog` and an unsupported reasoning\n  effort fails at `build()` instead of as a provider error mid-turn. The\n  catalog is a seam; agentyk ships no model list to go stale.\n- **Live tool progress** — a running tool calls\n  `ToolContext::report_progress`, and the host sees ephemeral `tool.progress`\n  events while it works. Results can carry structured `metadata` for the host\n  and image `parts` for the model, alongside the text both read. A tool can\n  also supply `display_name()` and phase-aware `narrate()` text, durably\n  captured on its start and completion events.\n- **MCP** — `McpCapability` connects to Model Context Protocol servers over\n  stdio or HTTP and exposes their tools to the model. `DynamicMcpCapability`\n  lets a host activate, deactivate, or atomically replace a server set; the\n  next turn sees the new snapshot without mutating the `Agent` or replacing\n  its session log. Both transports default\n  to `McpProtocolMode::Auto` and speak the stateless `2026-07-28` protocol\n  where they can — carrying the protocol version, client capabilities, and\n  identity in each request's `_meta` — falling back to the initialize\n  handshake for a server from an earlier revision. HTTP finds out from the\n  first request; stdio, which has no status codes to read, probes with\n  `server/discover`. `tools/list` is cached for the `ttlMs` the server\n  reports. `McpAuthProvider` supplies credentials per request. Feature\n  `mcp-oauth` adds OAuth 2.1 discovery, client identification (pre-registered,\n  Client ID Metadata Document, or dynamic registration), PKCE browser login\n  with RFC 9207 issuer validation, and automatic token refresh; the\n  application opens the returned URL and persists tokens.\n- **Steering** — `Session::input()` hands out a queue a UI can push to while a\n  turn is running; messages join the conversation at its next reasoning step.\n- **User hooks** — the six Everruns lifecycle points (`session_start`,\n  `user_prompt_submit`, `pre_tool_use`, `post_tool_use`, `turn_end`,\n  `session_end`) compose as ordinary `Hook` values. Prompt/tool hooks can\n  mutate or block; end hooks are advisory. The optional `hooks` feature adds\n  trusted local `ShellHook`s using the same structured decision contract.\n- **Multi-actor sessions** — route an addressed turn to another by-value\n  `Agent` with `Session::run_with_agent` while retaining the shared replayable\n  history. `ExternalActor` distinguishes users from external channels, and\n  event metadata carries host-owned participant provenance.\n- **Concurrent tools** — a batch the model asked for in parallel runs in\n  parallel, with results still recorded in the order it asked.\n- **Providers over drivers** — a `ChatDriver` is one wire protocol\n  (OpenResponses, OpenAI Chat Completions, Anthropic Messages, all feature\n  `http`, plus a scripted `SimDriver` for offline tests); a `Provider` is a\n  service that speaks one, owning the endpoint and the credentials. So one\n  driver serves OpenAI, a gateway, and a local runtime at once, credentials\n  refresh per request through `ProviderAuth`, and a `ModelSpec` stays plain\n  config that names a service without carrying its key. `providers::openai`\n  speaks OpenResponses — reasoning summaries round-trip and gateways that\n  serve the standard work unchanged; Chat Completions is one\n  `.with_driver(OpenAiDriver::new())` away. The Anthropic driver places\n  prompt-cache breakpoints by default, so a long session does not pay full\n  price to re-send its own transcript.\n\n## Multi-actor demo\n\n[`examples/osbb`](examples/osbb) seats five co-owners of an apartment building\nin one conversation with the agent that answers for their association: two of\nthem report the same night noise from different apartments, and the association\nanswers both on the same session. Each input keeps its named `ExternalActor`,\nand the model sees speaker labels without those labels rewriting durable\nhistory.\n\n<img src=\"examples/osbb/docs/demo.gif\" width=\"880\" alt=\"Olena reports night music from apartment 41, Petro confirms it from another apartment, and the Manager logs the two reports separately, cites quiet hours, and puts the matter on the board agenda.\">\n\n<sup>Real `openai/gpt-5.6-terra` run. See the example README for the offline\ntests and recording recipe.</sup>\n\n## Comprehensive example\n\n[`production_agent.rs`](crates/agentyk/examples/production_agent.rs) composes a\ntyped tool, a pre-tool safety hook, durable `JsonlEventLog` storage, multiple\nturns, and replay-based resume. It stays deterministic and offline:\n\n```sh\ncargo run -p agentyk --example production_agent\n```\n\n## Background hosting examples\n\nThese examples keep task lifecycle in the application, matching the boundary\nused by Everruns and Yolop, while Agentyk runs the parent and child turns:\n\n- [`github_monitor.rs`](crates/agentyk/examples/github_monitor.rs) detaches\n  `gh pr checks --watch`, ends the foreground turn, and wakes the same session\n  when the command finishes. It needs an authenticated `gh` CLI:\n  `cargo run -p agentyk --example github_monitor -- OWNER/REPO PR_NUMBER`.\n- [`subagents.rs`](crates/agentyk/examples/subagents.rs) starts five independent\n  child-agent sessions, returns their task ids, and has the parent wait for all\n  five: `cargo run -p agentyk --example subagents`.\n\n## Packaging\n\nFour crates separate portable contracts, canonical turn semantics, proc\nmacros, and bundled implementations:\n\n- **`agentyk-core`** — the contract: what you *implement against*. Traits\n  and portable values, events, and turn reducers.\n- **`agentyk-engine`** — the canonical step engine, `Agent`, `Session`, and\n  in-process runner. Everruns durable execution hosts this same engine one\n  persisted step at a time.\n- **`agentyk-macros`** — attribute macros re-exported by the facade, including\n  `#[agentyk::tool]`. Applications do not depend on it directly.\n- **`agentyk`** — the application facade and bundled feature-gated modules:\n  drivers and their ready-made providers, event stores, MCP, and filesystem\n  support.\n\nMCP and filesystem are first-class parts of the library, not separate\nintegration crates. Other integrations also stay as modules for now. See\n[`docs/architecture.md`](docs/architecture.md) for the execution and\ndurability model.\n\n## Features\n\n`agentyk` ships with **no features on by default**: the bare crate gives you\nthe turn loop, the event logs, and the offline `SimDriver`, and pulls in\nnothing that can open a socket or spawn a process. Opt in to what you need.\n\n| Feature | Adds | Pulls in |\n| --- | --- | --- |\n| *(none)* | turn loop, `InMemoryEventLog`, `JsonlEventLog`, `SimDriver` | — |\n| `http` | `OpenResponsesDriver`, `OpenAiDriver`, `AnthropicDriver` (SSE streaming) | `reqwest`, `futures-util` |\n| `mcp` | static or live-reloadable MCP capabilities / `McpClient` over stdio (HTTP transport also needs `http`) | `tokio` (rt, process, io-util, sync, time) |\n| `mcp-oauth` | OAuth 2.1 discovery, DCR, PKCE loopback login, token refresh | `mcp`, `http`, `base64`, `rand`, `sha2` |\n| `fs` | `FileSystemCapability`, real-disk and in-memory stores | `tokio` (fs, sync), `regex` |\n| `hooks` | trusted local `ShellHook` executor | `tokio` (process, io-util, time) |\n| `full` | all of the above | all of the above |\n\n```toml\nagentyk = { version = \"0.1\", features = [\"http\", \"fs\"] }\n```\n\n## Hooks\n\nImplement `Hook` for an in-process callback and attach it by value:\n\n```rust\nuse agentyk::{\n    Hook, HookEvent, HookOutcome, HookPayload,\n};\nuse async_trait::async_trait;\n\nstruct ProtectDeploy;\n\n#[async_trait]\nimpl Hook for ProtectDeploy {\n    fn id(&self) -> &str { \"protect-deploy\" }\n    fn event(&self) -> HookEvent { HookEvent::PreToolUse }\n\n    async fn run(&self, payload: &HookPayload) -> HookOutcome {\n        if payload.data[\"tool_name\"] == \"deploy\" {\n            HookOutcome::Block {\n                reason: \"deploy requires approval\".into(),\n                user_message: Some(\"Approve the deployment first.\".into()),\n            }\n        } else {\n            HookOutcome::Allow\n        }\n    }\n}\n\nlet agent = Agent::builder()\n    // model + provider + tools...\n    .hook(ProtectDeploy)\n    .build()?;\n```\n\nHooks with the same event run in attachment order. A prompt mutation replaces\n`patch.message`; a pre-tool mutation shallow-merges `patch.arguments`; a\npost-tool mutation may replace `patch.result` / `patch.error` or append\n`patch.additional_context`. The first prompt/pre-tool block stops that action.\n`post_tool_use`, `turn_end`, and session lifecycle events are advisory because\nthe observed side effect has already happened (or has no blockable action).\n\nWith feature `hooks`, `ShellHook` runs a trusted local command:\n\n```rust\nuse agentyk::{HookErrorPolicy, HookEvent, ShellHook};\n\nlet lint = ShellHook::new(\n    \"lint-after-edit\",\n    HookEvent::PostToolUse,\n    \"scripts/lint-hook.sh\",\n)\n.on_error(HookErrorPolicy::Warn);\n```\n\nThe command receives a JSON `HookPayload` on stdin and in\n`AGENTYK_HOOK_PAYLOAD_JSON`, plus convenience `AGENTYK_HOOK_*` variables. It\nreturns JSON such as `{\"decision\":\"allow\"}` or\n`{\"decision\":\"mutate\",\"patch\":{...}}`; empty stdout uses the exit code as a\nGit-hook-style allow/block decision. Execution is capped at 30 seconds and\n64 KiB of output. `ShellHook` uses `/bin/sh` with the application's OS\npermissions—it is deliberately not presented as a sandbox. A server or\ndurable host should implement `Hook` over its own sandboxed executor.\n\nBecause `Agent::session()` is synchronous, `session_start` fires immediately\nbefore the new session's first turn. Async `session_end` hooks fire from the\nexplicit, idempotent `Session::close().await?`; dropping a session cannot await.\nA resumed non-empty session does not replay `session_start`; `session_end`\nonly runs when that handle is explicitly closed.\n\nA durable host can retry a hook if it crashes after the external command ran\nbut before the resulting events were committed—the same at-least-once boundary\nas a tool call. Side-effecting hooks should use `hook_id` plus session/turn/tool\nids as an idempotency key when duplicate execution matters.\n\n## Try it (offline, no API key)\n\n```sh\ncargo run -p agentyk --example hello\ncargo test --workspace --all-features\n```\n\n## Inspect and fork history\n\nEvery durable head is an immutable `SessionPoint`. Inspection is read-only;\nforking creates a new session and leaves the original branch intact.\n\n```rust\nlet point = session.point().await?;\nlet past = session.inspect(point).await?;\nprintln!(\"{} messages\", past.messages().len());\n\nlet mut alternative = session.fork(point).await?;\nalternative.run(\"try a different approach\").await?;\n```\n\nForks are accepted at empty or completed-turn boundaries. Mid-turn points are\nstill inspectable, but cannot be continued as a new branch because doing so\ncould repeat a partially completed external action.\n\nAfter a process failure, `session.resume_pending().await?` continues an\nincomplete turn only at the current head. Tool execution is at-least-once\nacross that recovery boundary, so side-effecting tools should use idempotency\nkeys when a host requires deduplication.\n\n## A real application\n\n[`examples/codenko`](examples/codenko) is a small terminal coding agent —\nfilesystem tools, a shell tool behind an approval prompt, streaming output,\ncancellable turns — in about 1,450 lines. It is the short version of what\nbuilding on agentyk looks like: the whole UI is a fold over the event stream,\nso it is tested without a terminal or a network.\n\n```sh\nANTHROPIC_API_KEY=... cargo run --release -p codenko -- --dir path/to/project\n```\n\n## Relationship to everruns\n\nThe domain language — events protocol, capabilities, drivers, the turn\ncontract — is inherited from [everruns](https://github.com/everruns/everruns).\nagentyk is the value-first core; the plan is to rebuild `everruns-core` and\n`everruns-runtime` on top of it, with identity, persistence, and multitenancy\nlayered on by hosts rather than baked into the model. See\n[`knowledge/foundations/plan.md`](knowledge/foundations/plan.md).\n\n## License\n\nMIT\n",
  "bytes": 16134,
  "sha": "91602186e17bdcacc2aec991909c94edd82b856798dfaf35d3b770f51d8e45ac",
  "repo_slug": "everruns/agentyk",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_everruns_agentyk_knowledge_index_md_25099a95/readme"
}