Skip to main content

KCS Agent

A typed, provider-neutral Python agent loop with streaming output and composable extensions. Python 3.12+ · MIT · No LangChain dependency.

Install

uv add kcs-agent
# or: pip install kcs-agent

Quick start

import asyncio
import os

from kcs_agent import Agent, DeepSeekProvider, ReasoningEffort, UserMessage, UserMessageData


async def main() -> None:
    model = DeepSeekProvider(model="deepseek-v4-flash", api_key=os.environ["DEEPSEEK_API"])
    try:
        result = await Agent(model).run(
            UserMessage(data=UserMessageData("Explain an agent loop in one sentence.")),
            reasoning_effort=ReasoningEffort.OFF,
        )
        print(result.message.data.content)
    finally:
        await model.aclose()


asyncio.run(main())

Each invocation supplies the latest input. The core does not retrieve earlier conversations. It owns the bounded model/tool loop, ordered events, cancellation state, and usage accumulation.

Tools and system-prompt guidance

A tool remains a regular typed function. Its first docstring paragraph is its description. The bare @tool form reads Snippet:, Guidelines:, and Args: from the docstring:

from kcs_agent import Agent, ToolPromptExtension, tool


@tool
def add(left: int, right: int) -> int:
    """Add two integer values.

    Snippet:
        add(left, right) -> sum

    Guidelines:
        - Use for exact integer addition.

    Args:
        left: First integer.
        right: Second integer.

    Examples:
        >>> add(2, 3)
        5
    """
    return left + right


def build_agent(model):
    return Agent(
        model,
        system_prompt="Answer accurately.",
        extensions=[ToolPromptExtension([add])],
    )

The configured form, @tool(snippet="...", guideline="..."), overrides docstring guidance. Examples: documents Python usage; it is never inserted into the model prompt.

ToolPromptExtension registers tools and appends their guidance to the end of the combined system instructions, first all snippets, then all guidelines. It derives each model request from unchanged run messages, preventing duplicate guidance in repeated tool rounds. Place it after other prompt-transforming extensions.

ToolExtension registers tools without modifying the prompt. parse_tool(function) exports the callable's input and return JSON Schemas. Arguments are validated and nested Pydantic models are constructed before invocation. Synchronous tools run in a worker thread. Cancellation does not forcibly terminate an already-running synchronous function; applications must provide cooperative cancellation for long-running side effects.

History extension

HistoryExtension invokes an async loader once per run. The loader receives typed AgentContext[SessionDataT], including the stable session ID. It returns preceding messages, optionally a context snapshot followed by its replay tail. Exclude the current input, even if the application already persisted it.

from kcs_agent import (
    Agent, AgentContext, AnyMessage, HistoryExtension, SessionState,
    ToolPromptExtension, UserMessage, UserMessageData,
)

history: dict[str, list[AnyMessage]] = {}


async def load_history(context: AgentContext[None]) -> list[AnyMessage]:
    return list(history.get(context.state.session.session_id, ()))


async def conversation(model) -> None:
    session = SessionState(data=None)
    agent = Agent(model, extensions=[HistoryExtension(load_history), ToolPromptExtension([add])])
    for text in ("Use add to compute 2+3.", "What was the result?"):
        result = await agent.run(UserMessage(data=UserMessageData(text)), session=session)
        history[session.session_id] = [
            message for message in result.state.run.messages if message.role != "system"
        ]

This example stores history in memory. Production applications own durable storage and compaction through the loader and lifecycle hooks. Different sessions never share mutable run state through the agent instance.

Streaming and hooks

Agent.stream(...) emits ordered AgentEvent values. Relevant event types include:

  • TEXT_DELTA, REASONING_DELTA, and TOOL_CALL_DELTA
  • MODEL_STARTED and MODEL_COMPLETED
  • TOOL_STARTED, TOOL_COMPLETED, and TOOL_FAILED
  • RUN_COMPLETED, RUN_FAILED, and cooperative RUN_CANCELLED

Tool deltas are display-only fragments. The final model response contains authoritative complete tool calls. Models may answer without calling any tool.

An extension can implement load_state, on_run_start, context_messages, tools, before_model, after_model, before_tool, after_tool, on_message, on_checkpoint, on_error, on_run_end, and release_state. Hooks execute in registration order. before_model may return a replacement immutable ModelRequest; returning None leaves it unchanged. on_message observes generated assistant/tool messages; the application owns persistence of caller-supplied input.

Task cancellation and closing the stream checkpoint partial state and release resources. Task cancellation propagates asyncio.CancelledError; it cannot yield a terminal event to an already disconnected consumer.

Messages and providers

Messages separate semantic data from typed metadata using Message[DataT, MetadataT]. Concrete roles are system, user, assistant, and tool. User content accepts text and ordered image blocks with URL, bytes, or opaque asset sources. Applications must resolve opaque asset IDs to actual images when visual understanding is required. Ollama accepts encoded image bytes or base64 data URLs; remote images must be downloaded by the application first.

Official SDK adapters are included:

Adapter SDK Notes
OpenAIProvider OpenAI Chat Completions; configurable base URL and temperature
DeepSeekProvider OpenAI Thinking and reasoning replay; configurable base URL and temperature
AnthropicProvider Anthropic Configurable base URL; signed thinking and tool-result replay
GoogleProvider Google GenAI Text, images, function calls, and signed thinking parts
OllamaProvider Ollama Configurable local base URL; incremental NDJSON streaming

Adapters use the operating system certificate trust store. Each accepts an optional httpx.AsyncBaseTransport for isolated tests and exposes aclose().

Reasoning effort is supplied per run using ReasoningEffort: off, minimal, low, medium, high, and xhigh. Provider capabilities differ. DeepSeek V4 maps minimal/low to low, medium/high to high, and xhigh to max. Anthropic and Google use token budgets. OpenAI forwards enabled levels; unsupported model/level combinations may be rejected by the provider. Ollama maps enabled levels to its boolean thinking switch.

For structured output, ModelRequest.tool_choice names a schema-bound response tool. OpenAI, Anthropic, and Google force that choice; Ollama receives an explicit instruction. Consumers must validate the completed tool arguments against their output model.

Development

uv sync
uv run ruff check src tests examples
uv run ruff format --check src tests examples
uv run pytest

# Real DeepSeek streams and tool round trips via both protocols:
KCS_AGENT_LIVE_TESTS=1 uv run pytest tests/test_live_providers.py

Live tests require DEEPSEEK_API and default to deepseek-v4-flash. Optional overrides: DEEPSEEK_API_MODEL, DEEPSEEK_ANTHROPIC_MODEL, DEEPSEEK_ANTHROPIC_BASE_URL, and DEEPSEEK_ANTHROPIC_API_KEY. Network and provider failures fail explicitly when live tests are enabled.

uv build
uv publish dist/kcs_agent-0.1.0.tar.gz dist/kcs_agent-0.1.0-py3-none-any.whl

Supply publishing credentials through your local credential store or UV_PUBLISH_TOKEN.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kcs_agent-0.1.0.tar.gz (28.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kcs_agent-0.1.0-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

File details

Details for the file kcs_agent-0.1.0.tar.gz.

File metadata

  • Download URL: kcs_agent-0.1.0.tar.gz
  • Upload date:
  • Size: 28.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for kcs_agent-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d8d7328299973f3574151df3fd6c5749a2c9475dfd8fecd84825ea11e9f3cce5
MD5 899d902548696fc1850b8a9039222857
BLAKE2b-256 eca491cc4f5f483662339b1781bb7b3e864c596688964a0cffaf8351de9c0cc9

See more details on using hashes here.

File details

Details for the file kcs_agent-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: kcs_agent-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 34.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for kcs_agent-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9f17aade36dc746c3c7af9e6a8a4377e8107c6701fb081252a9f03419e09f536
MD5 ff1a33b2920fc4cc18bb250594aab262
BLAKE2b-256 7cfb5e2d1acc1a9c1825558a3579de9df89c1f6697c6b521a9493289b024e02b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page