Skip to content

Commit b931084

Browse files
authored
Merge pull request #30 from irshadnilam/runtime-fixes
feat(radkit): Single-agent runtime with better api structure
2 parents f1585af + ba6b829 commit b931084

40 files changed

Lines changed: 1089 additions & 1360 deletions

README.md

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ schemars = "1"
4848

4949
#### With Agent Server Runtime
5050

51-
To include the `DefaultRuntime` and enable the full A2A agent server capabilities (on native targets), enable the `runtime` feature:
51+
To include the runtime server handle and enable the full A2A agent server capabilities (on native targets), enable the `runtime` feature:
5252

5353
```toml
5454
[dependencies]
@@ -63,7 +63,7 @@ schemars = "1"
6363

6464
Radkit ships optional capabilities that you can opt into per target:
6565

66-
- `runtime`: Enables the native `DefaultRuntime`, HTTP server, tracing, and other dependencies required to run A2A-compliant agents locally.
66+
- `runtime`: Enables the native runtime handle, HTTP server, tracing, and other dependencies required to run A2A-compliant agents locally.
6767
- `dev-ui`: Builds on top of `runtime` and serves an interactive UI (native-only) where you can trigger tasks, and inspect streaming output.
6868

6969
## Core Concepts
@@ -924,31 +924,27 @@ impl SkillHandler for ReportGeneratorSkill {
924924
```rust
925925
use radkit::agent::{Agent, AgentDefinition};
926926
use radkit::models::providers::AnthropicLlm;
927-
use radkit::runtime::DefaultRuntime;
927+
use radkit::runtime::Runtime;
928928

929-
pub fn configure_agents() -> Vec<AgentDefinition> {
930-
let my_agent = Agent::builder()
929+
pub fn configure_agent() -> AgentDefinition {
930+
Agent::builder()
931931
.with_id("my-agent-v1")
932932
.with_name("My A2A Agent")
933933
.with_description("An intelligent agent with multiple skills")
934934
// Skills automatically provide metadata from #[skill] macro
935935
.with_skill(ProfileExtractorSkill)
936936
.with_skill(ReportGeneratorSkill)
937937
.with_skill(DataAnalysisSkill)
938-
.build();
939-
940-
vec![my_agent]
938+
.build()
941939
}
942940

943941
// Local development
944942
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
945943
#[tokio::main]
946944
async fn main() -> Result<(), Box<dyn std::error::Error>> {
947945
let llm = AnthropicLlm::from_env("claude-sonnet-4-5-20250929")?;
948-
let runtime = DefaultRuntime::new(llm);
949-
950-
runtime
951-
.agents(configure_agents())
946+
Runtime::builder(configure_agent(), llm)
947+
.build()
952948
.serve("127.0.0.1:8080")
953949
.await?;
954950

docs/src/content/docs/a2a/composing-agents.md

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,24 @@ use radkit::runtime::Runtime;
3535
# }
3636

3737

38-
// This function defines your agents.
39-
pub fn configure_agents() -> Vec<AgentDefinition> {
40-
let my_agent = Agent::builder()
38+
// This function defines your agent.
39+
pub fn configure_agent() -> AgentDefinition {
40+
Agent::builder()
4141
.with_id("my-hr-agent-v1")
4242
.with_name("HR Assistant Agent")
4343
.with_description("An intelligent agent for handling HR tasks like resume processing and report generation.")
4444
// Add the skills to the agent
4545
.with_skill(ProfileExtractorSkill)
4646
.with_skill(ReportGeneratorSkill)
47-
.build();
48-
49-
// You can define and return multiple agents from the same project
50-
vec![my_agent]
47+
.build()
5148
}
5249
```
5350

5451
The `Agent::builder()` creates a serializable `AgentDefinition`. This definition is the blueprint for your agent that gets deployed.
5552

5653
## Running the Agent Locally
5754

58-
To test your agent, you can run it locally. The `DefaultRuntime` provides a simple, A2A-compliant web server for this purpose.
55+
To test your agent, you can run it locally. The runtime provides a simple, A2A-compliant web server for this purpose.
5956

6057
To enable the server, you must enable the `runtime` feature for Radkit in your `Cargo.toml`:
6158

@@ -70,21 +67,18 @@ Then, you can add a `main` function to run the server.
7067
```rust
7168
# use radkit::agent::AgentDefinition;
7269
# use radkit::models::providers::AnthropicLlm;
73-
# use radkit::runtime::DefaultRuntime;
74-
# pub fn configure_agents() -> Vec<AgentDefinition> { vec![] }
70+
# use radkit::runtime::Runtime;
71+
# pub fn configure_agent() -> AgentDefinition { unimplemented!() }
7572
// This main function will only be compiled for native targets, not for WASM.
7673
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
7774
#[tokio::main]
7875
async fn main() -> Result<(), Box<dyn std::error::Error>> {
7976
// 1. Create an LLM instance
8077
let llm = AnthropicLlm::from_env("claude-sonnet-4-5-20250929")?;
8178

82-
// 2. Create a default runtime environment with the LLM
83-
let runtime = DefaultRuntime::new(llm);
84-
85-
// 3. Add the agents and start the server
86-
runtime
87-
.agents(configure_agents())
79+
// 2. Create a runtime environment with the agent + LLM
80+
Runtime::builder(configure_agent(), llm)
81+
.build()
8882
.serve("127.0.0.1:8080")
8983
.await?;
9084

docs/src/content/docs/architecture/deployment.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ Once the `.wasm` module is uploaded, the cloud platform takes over:
2727

2828
1. **Instantiation**: The platform loads the `.wasm` module into a high-performance, secure WASM runtime (like Wasmtime).
2929

30-
2. **Discovery via Configuration Function**: The runtime calls a known function in the module to retrieve the `AgentDefinition`s you have defined. This is typically the `configure_agents()` function.
30+
2. **Discovery via Configuration Function**: The runtime calls a known function in the module to retrieve the `AgentDefinition` you have defined. This is typically the `configure_agent()` function.
3131

3232
```rust
33-
pub fn configure_agents() -> Vec<AgentDefinition> {
34-
// ... return agent definitions
33+
pub fn configure_agent() -> AgentDefinition {
34+
// ... return an agent definition
3535
}
3636
```
3737

@@ -44,4 +44,4 @@ Once the `.wasm` module is uploaded, the cloud platform takes over:
4444
- **Speed**: `.wasm` files are small and can be uploaded and started in seconds. WASM runtimes have near-zero cold start times, allowing agents to scale down to zero and respond to requests instantly.
4545
- **Security**: Every agent runs in its own secure, memory-safe sandbox, completely isolated from other agents. The WASI interface ensures that the agent can only access the resources it has been explicitly granted permission to use.
4646
- **Portability**: The same `.wasm` file can be run on any server that has a compliant WASM runtime, regardless of the underlying CPU architecture or operating system.
47-
- **Scalability**: The lightweight nature of WASM instances allows for massive scaling, with thousands of agents running concurrently on a single physical machine.
47+
- **Scalability**: The lightweight nature of WASM instances allows for massive scaling, with thousands of agents running concurrently on a single physical machine.
Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,80 @@
11
---
22
title: Runtime
3-
description: Learn about Radkit's Runtime trait and how it provides service abstraction for agents.
3+
description: Learn about Radkit's AgentRuntime trait and how it provides service abstraction for agents.
44
---
55

66

77

8-
The core of Radkit's architecture is the `Runtime` trait. It acts as a centralized **service hub** or dependency injection container for your agent. Instead of passing many different service handles (like a database connector, a logger, etc.) to every function, your `SkillHandler`s receive a single, unified reference: `&dyn Runtime`.
8+
The core of Radkit's architecture is the `AgentRuntime` trait. It acts as a centralized **service hub** or dependency injection container for your agent. Instead of passing many different service handles (like a database connector, a logger, etc.) to every function, your `SkillHandler`s receive a single, unified reference: `&dyn AgentRuntime`.
99

1010
This design keeps your agent logic clean, decoupled from infrastructure, and highly portable.
1111

12-
## The `Runtime` Trait
12+
## The `AgentRuntime` Trait
1313

14-
All runtime environments, whether for local development or cloud deployment, must implement the `Runtime` trait. This guarantees a consistent interface for your skills.
14+
All runtime environments, whether for local development or cloud deployment, must implement the `AgentRuntime` trait. This guarantees a consistent interface for your skills.
1515

1616
```rust
17-
pub trait Runtime {
18-
fn task_manager(&self) -> Arc<dyn TaskManager>;
19-
fn memory_service(&self) -> Arc<dyn MemoryService>;
20-
fn logging_service(&self) -> Arc<dyn LoggingService>;
21-
// ... other services
17+
pub trait AgentRuntime {
18+
fn auth(&self) -> Arc<dyn AuthService>;
19+
fn memory(&self) -> Arc<dyn MemoryService>;
20+
fn logging(&self) -> Arc<dyn LoggingService>;
21+
fn default_llm(&self) -> Arc<dyn BaseLlm>;
2222
}
2323
```
2424

2525
Your skill code is written against this abstraction, not a concrete implementation. A skill simply asks the runtime for a service:
2626

2727
```rust
28-
// Inside a SkillHandler method...
29-
let memory_service = runtime.memory_service();
30-
memory_service.save(auth_ctx, "some_key", &data).await?;
28+
let memory = runtime.memory();
29+
memory.save(auth_ctx, "some_key", &data).await?;
3130
```
3231

3332
The skill doesn't know or care if `memory_service` is writing to an in-memory map for local testing or a massive, persistent cloud database.
3433

3534
## Core Services
3635

37-
The `Runtime` provides access to a set of essential services, each defined by its own trait.
36+
`AgentRuntime` provides access to a set of essential services, each defined by its own trait.
3837

39-
- **`TaskManager`**: A Data Access Object (DAO) for persisting and retrieving the state of A2A `Task`s and their event histories. This is crucial for multi-turn conversations and auditing.
38+
- **`TaskManager`**: A Data Access Object (DAO) for persisting and retrieving the state of A2A `Task`s and their event histories. This is crucial for multi-turn conversations and auditing. The core runtime uses it internally so skill authors don't have to.
4039
- **`MemoryService`**: Provides a persistent, tenant-aware key-value store. This is ideal for giving your agent long-term memory or for implementing Retrieval-Augmented Generation (RAG).
4140
- **`LoggingService`**: A structured logging interface that streams logs to the console during local development or to a cloud observability UI in production.
4241
- **`AuthService`**: Identifies the current user and tenant, ensuring that all other services are properly namespaced and data is kept secure.
4342

4443
## Provided Runtimes
4544

46-
Radkit provides a `DefaultRuntime` for local development, and the architecture allows for custom runtimes for production or specialized environments.
45+
Radkit provides a `RuntimeBuilder` plus a concrete `Runtime` implementation for local development, and the architecture allows for custom runtimes for production or specialized environments.
4746

48-
### `DefaultRuntime`
47+
### `Runtime` (native implementation)
4948

50-
Included with the `runtime` feature flag, `DefaultRuntime` is an out-of-the-box implementation that works on both native and WASM targets. It provides simple, in-memory versions of all the core services, allowing you to get up and running instantly with no configuration.
49+
Included with the `runtime` feature flag, the builder returns an out-of-the-box runtime that works on both native and WASM targets. It provides simple, in-memory versions of all the core services, allowing you to get up and running instantly with no configuration.
50+
51+
```rust
52+
use radkit::models::providers::OpenRouterLlm;
53+
use radkit::runtime::Runtime;
54+
55+
# fn configure_agent() -> radkit::agent::AgentDefinition {
56+
# radkit::agent::Agent::builder()
57+
# .with_id("hr-agent")
58+
# .with_name("HR Agent")
59+
# .build()
60+
# }
61+
62+
#[tokio::main]
63+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
64+
let llm = OpenRouterLlm::from_env("anthropic/claude-3.5-sonnet")?;
65+
Runtime::builder(configure_agent(), llm)
66+
.build()
67+
.serve("127.0.0.1:8080")
68+
.await?;
69+
Ok(())
70+
}
71+
```
72+
73+
The runtime hosts **exactly one agent definition** per process. When `.serve(..)` is called it automatically exposes the A2A HTTP surface at the root of the bound address:
74+
75+
- `/.well-known/agent-card.json` – serves the agent card
76+
- `/rpc` – JSON-RPC entry point for `message/send`, `message/stream`, `tasks/resubscribe`, etc.
77+
- `/message:stream` – streaming API for Server-Sent Events
78+
- `/tasks/{task_id}/subscribe` – SSE resubscribe endpoint that mirrors the A2A spec
79+
80+
If the optional `dev-ui` feature is enabled, the same server also mounts `/ui/*` routes and serves the React console for the single configured agent.

examples/hr_agent/agent.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use radkit::agent::{Agent, AgentDefinition};
22
use radkit::models::providers::OpenRouterLlm;
3-
use radkit::runtime::DefaultRuntime;
3+
use radkit::runtime::Runtime;
44

55
// --- Skill Implementations ---
66
// The modular skills are defined in their own files.
@@ -9,8 +9,8 @@ use hr_agent_skills::create_it_accounts::CreateItAccountsSkill;
99
use hr_agent_skills::generate_onboarding_plan::GenerateOnboardingPlanSkill;
1010
use hr_agent_skills::summarize_resume::SummarizeResumeSkill;
1111

12-
pub fn configure_agents() -> Vec<AgentDefinition> {
13-
let hr_agent = Agent::builder()
12+
pub fn configure_agent() -> AgentDefinition {
13+
Agent::builder()
1414
.with_id("hr-agent-v1")
1515
.with_name("HR Agent")
1616
.with_description(
@@ -22,26 +22,17 @@ pub fn configure_agents() -> Vec<AgentDefinition> {
2222
.with_skill(SummarizeResumeSkill)
2323
.with_skill(GenerateOnboardingPlanSkill)
2424
.with_skill(CreateItAccountsSkill)
25-
.build();
26-
27-
vec![hr_agent]
25+
.build()
2826
}
2927

3028
/// The main entry point for local, native development and testing.
3129
/// This function is excluded from WASM builds via the `#[cfg]` attribute.
3230
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
3331
#[tokio::main]
3432
async fn main() -> Result<(), Box<dyn std::error::Error>> {
35-
// DefaultRuntime requires an LLM instance
3633
let llm = OpenRouterLlm::from_env("anthropic/claude-3.5-sonnet")?;
37-
let runtime = DefaultRuntime::new(llm);
38-
39-
// The `serve` method on the local runtime takes the agent definitions
40-
// and starts a local A2A-compliant web server.
41-
// Agent metadata is automatically extracted from #[skill] macros
42-
// and exposed via the agent card endpoint.
43-
runtime
44-
.agents(configure_agents())
34+
Runtime::builder(configure_agent(), llm)
35+
.build()
4536
.serve("127.0.0.1:8080")
4637
.await?;
4738

examples/hr_agent/hr_agent_skills/create_it_accounts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use radkit::errors::AgentError;
55
use radkit::macros::skill;
66
use radkit::models::Content;
77
use radkit::runtime::context::{Context, TaskContext};
8-
use radkit::runtime::Runtime;
8+
use radkit::runtime::AgentRuntime;
99

1010
// --- Skill Logic Implementation ---
1111

@@ -33,7 +33,7 @@ impl SkillHandler for CreateItAccountsSkill {
3333
&self,
3434
task_context: &mut TaskContext,
3535
_context: &Context,
36-
_runtime: &dyn Runtime,
36+
_runtime: &dyn AgentRuntime,
3737
content: Content,
3838
) -> Result<OnRequestResult, AgentError> {
3939
// Extract role and name from content

examples/hr_agent/hr_agent_skills/generate_onboarding_plan.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use radkit::macros::skill;
77
use radkit::models::providers::OpenRouterLlm;
88
use radkit::models::Content;
99
use radkit::runtime::context::{Context, TaskContext};
10-
use radkit::runtime::{MemoryServiceExt, Runtime};
10+
use radkit::runtime::{AgentRuntime, MemoryServiceExt};
1111

1212
fn generate_onboarding_tasks() -> LlmFunction<Vec<String>> {
1313
let llm = OpenRouterLlm::from_env("anthropic/claude-3.5-sonnet")
@@ -44,7 +44,7 @@ impl SkillHandler for GenerateOnboardingPlanSkill {
4444
&self,
4545
task_context: &mut TaskContext,
4646
context: &Context,
47-
runtime: &dyn Runtime,
47+
runtime: &dyn AgentRuntime,
4848
_content: Content,
4949
) -> Result<OnRequestResult, AgentError> {
5050
// Send intermediate update

examples/hr_agent/hr_agent_skills/summarize_resume.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use radkit::macros::{skill, tool, LLMOutput};
88
use radkit::models::providers::OpenRouterLlm;
99
use radkit::models::Content;
1010
use radkit::runtime::context::{Context, TaskContext};
11-
use radkit::runtime::{MemoryServiceExt, Runtime};
12-
use radkit::tools::ToolResult;
11+
use radkit::runtime::{AgentRuntime, MemoryServiceExt};
12+
use radkit::tools::{BaseTool, FunctionTool, ToolResult};
1313
use schemars::JsonSchema;
1414
use serde::{Deserialize, Serialize};
1515
use serde_json::json;
@@ -164,7 +164,7 @@ impl SkillHandler for SummarizeResumeSkill {
164164
&self,
165165
task_context: &mut TaskContext,
166166
context: &Context,
167-
runtime: &dyn Runtime,
167+
runtime: &dyn AgentRuntime,
168168
content: Content,
169169
) -> Result<OnRequestResult, AgentError> {
170170
// Send intermediate status update
@@ -230,7 +230,7 @@ impl SkillHandler for SummarizeResumeSkill {
230230
&self,
231231
task_context: &mut TaskContext,
232232
_context: &Context,
233-
runtime: &dyn Runtime,
233+
runtime: &dyn AgentRuntime,
234234
content: Content,
235235
) -> Result<OnInputResult, AgentError> {
236236
let slot: ResumeInputSlot = task_context.load_slot()?.unwrap();

radkit-macros/tests/skill_tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use radkit::agent::{OnRequestResult, RegisteredSkill, SkillHandler};
22
use radkit::errors::AgentError;
33
use radkit::models::Content;
44
use radkit::runtime::context::{Context, TaskContext};
5-
use radkit::runtime::Runtime;
5+
use radkit::runtime::AgentRuntime;
66
use radkit_macros::skill;
77

88
// Test 1: Basic skill with required fields only
@@ -22,7 +22,7 @@ impl SkillHandler for TestSkill {
2222
&self,
2323
_task_context: &mut TaskContext,
2424
_context: &Context,
25-
_runtime: &dyn Runtime,
25+
_runtime: &dyn AgentRuntime,
2626
_content: Content,
2727
) -> Result<OnRequestResult, AgentError> {
2828
Ok(OnRequestResult::Completed {
@@ -57,7 +57,7 @@ impl SkillHandler for FullSkill {
5757
&self,
5858
_task_context: &mut TaskContext,
5959
_context: &Context,
60-
_runtime: &dyn Runtime,
60+
_runtime: &dyn AgentRuntime,
6161
_content: Content,
6262
) -> Result<OnRequestResult, AgentError> {
6363
Ok(OnRequestResult::Completed {
@@ -92,7 +92,7 @@ impl SkillHandler for EmptyArraysSkill {
9292
&self,
9393
_task_context: &mut TaskContext,
9494
_context: &Context,
95-
_runtime: &dyn Runtime,
95+
_runtime: &dyn AgentRuntime,
9696
_content: Content,
9797
) -> Result<OnRequestResult, AgentError> {
9898
Ok(OnRequestResult::Completed {

0 commit comments

Comments
 (0)