agentkit-core 0.2.0

Core transcript, content, usage, and cancellation primitives for agentkit.
Documentation

agentkit-core

Shared primitives for agentkit transcripts, content parts, usage accounting, identifiers, and cancellation.

This crate defines the data model used across the rest of the workspace:

  • transcript items and roles
  • multimodal content parts
  • tool call and tool result payloads
  • streaming deltas
  • token and cost usage
  • cancellation checkpoints for turns and tools

Most other crates in the workspace depend on agentkit-core as their common language for messages and events.

Building a transcript for the agent loop

Every agent turn starts with a Vec<Item> transcript. System instructions, user messages, and context documents are all items; the loop appends assistant and tool items as the turn progresses.

use agentkit_core::{Item, ItemKind};

let transcript = vec![
    Item::text(ItemKind::System, "You are a careful coding agent."),
    Item::text(ItemKind::Context, "Project uses Rust 1.80, workspace has 12 crates."),
    Item::text(ItemKind::User, "Summarize the release notes."),
];

assert_eq!(transcript.len(), 3);
assert_eq!(transcript[0].kind, ItemKind::System);

Representing tool calls and results

When the model invokes a tool the loop emits a ToolCallPart. After execution the tool executor wraps the output in a ToolResultPart and appends it back to the transcript as a Tool item so the model can observe the result.

use agentkit_core::{
    Item, ItemKind, Part, ToolCallId, ToolCallPart, ToolOutput, ToolResultPart,
};
use serde_json::json;

// The model asks to read a file.
let tool_call = ToolCallPart::new(
    ToolCallId::new("call-1"),
    "fs.read_file",
    json!({ "path": "CHANGELOG.md" }),
);

// After execution, the tool executor produces a result item.
let tool_result_item = Item::new(
    ItemKind::Tool,
    vec![Part::ToolResult(ToolResultPart::success(
        tool_call.id.clone(),
        ToolOutput::text("## v0.3.0\n- Added compaction."),
    ))],
);

assert!(matches!(tool_result_item.parts[0], Part::ToolResult(_)));

Tracking token usage across turns

Usage and TokenUsage let you accumulate costs and token counts reported by model providers. Reporters and compaction triggers inspect these values to decide when to summarize or stop.

use agentkit_core::{CostUsage, TokenUsage, Usage};

let turn_usage = Usage::new(
    TokenUsage::new(1200, 350)
        .with_reasoning_tokens(50)
        .with_cached_input_tokens(800),
)
.with_cost(CostUsage::new(0.0042, "USD"));

let tokens = turn_usage.tokens.as_ref().unwrap();
assert_eq!(tokens.input_tokens + tokens.output_tokens, 1550);

Cancelling a running turn

Wire a CancellationController into the agent and spawn a Ctrl-C listener that fires interrupt(). The loop checks the handle between steps and returns FinishReason::Cancelled when triggered.

use agentkit_core::CancellationController;
use agentkit_loop::{Agent, LoopStep, SessionConfig};
use agentkit_core::FinishReason;
use agentkit_provider_openrouter::{OpenRouterAdapter, OpenRouterConfig};

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let cancellation = CancellationController::new();
let agent = Agent::builder()
    .model(OpenRouterAdapter::new(OpenRouterConfig::from_env()?)?)
    .cancellation(cancellation.handle())
    .build()?;

let mut driver = agent
    .start(SessionConfig::new("demo"))
    .await?;

// Ctrl-C fires the interrupt, cancelling the in-flight turn.
let interrupt = cancellation.clone();
let ctrl_c = tokio::spawn(async move {
    let _ = tokio::signal::ctrl_c().await;
    interrupt.interrupt();
});

// ... submit input and drive the loop ...

let step = driver.next().await;
ctrl_c.abort();

if let Ok(LoopStep::Finished(result)) = step {
    if result.finish_reason == FinishReason::Cancelled {
        eprintln!("turn cancelled");
    }
}
# Ok(())
# }