Skip to main content

samcoadk

A small Agent Development Kit. Four ideas, and nothing underneath them.

Agent one turn: build the prompt, call the model, run the tools it asks for, return the reply
Runner one turn for a user in a session: load the history, call the agent, commit the result
SessionService where conversations live — in memory, or in Mongo or MySQL
A2A one agent calling another, in-process or over HTTP

The core is stdlib only. import samcoadk pulls no third-party module, and there is a test that says so.

Quickstart

from samcoadk import Agent, Runner

def order_status(order_id: str) -> str:
    """Look up the status of a customer order."""
    return f"Order {order_id} shipped on Tuesday."

agent = Agent(
    name="support",
    model="gateway/openai/gpt-4.1",      # or "echo" to run with no API key
    instruction="You are a support agent. Answer in one or two sentences.",
    tools=[order_status],
)

runner = Runner(agent)
print(runner.run(user_id="u1", session_id="s1", message="where is order A9?"))

Durable conversations are one line:

from samcoadk import DatabaseSessionService

runner = Runner(agent, session_service=DatabaseSessionService("mongodb://localhost:27017"))
runner = Runner(agent, session_service=DatabaseSessionService("mysql://user:pw@host/db"))

Behind HTTP:

pip install "samcoadk[server]"
samcoadk serve examples.support          # /run, /run_sse, /sessions, /a2a, agent card
samcoadk chat  examples.support          # the same Runner, in your terminal

Install

pip install samcoadk                 # the agent, in memory
pip install "samcoadk[mongo]"        # + Mongo sessions
pip install "samcoadk[mysql]"        # + MySQL sessions
pip install "samcoadk[server]"       # + the HTTP surface
pip install "samcoadk[all]"

MCP needs nothing installed — samcoadk.mcp speaks stdio and streamable HTTP over stdlib urllib.

The one rule

An Agent holds no conversation. Build one and share it across every request. The transcript lives in a Session, and Runner is what binds the two — so two callers cannot leak into each other's history, because there is no shared history to leak through.

runner.run(user_id="alice", session_id="s", message="my card is 4111…")
runner.run(user_id="bob",   session_id="s", message="what did I say?")   # sees nothing of alice

Sessions

A session holds events (what happened, in order) and state (what is true now — a phase, a draft, a counter; whatever your agent needs).

They are written together, once per turn:

runner.run(user_id="u1", session_id="s1", message="...", state_delta={"phase": "PLAN"})

One atomic checkpoint per turn means a crash cannot leave a transcript that says a question was asked next to a state that says it was not.

Another database

Implement five methods — ensure_schema, read, write, list, delete — and pass it in. All the session rules stay in DatabaseSessionService:

runner = Runner(agent, session_service=DatabaseSessionService(backend=MyRedisBackend()))

Guardrails

samcoadk ships none, deliberately: what counts as an unacceptable request is your agent's policy, not the kit's. The seam is callbacks.py.

from samcoadk import CallbackDecision

class NoSelfApproval:
    def after_agent(self, ctx, reply):
        if '"approved"' in reply:
            return CallbackDecision.block("approval is the caller's to give",
                                          replacement_result="Rejected: cannot self-approve.")

agent = Agent(name="planner", model=..., callbacks=[NoSelfApproval()])

before_agent is the input rail, after_agent the output rail, and before_tool / after_tool police individual tool calls. A hook may pass a value through, replace it, or block. Callbacks fail closed by default — set fail_open = True on one that is pure observability.

Agents calling agents

A remote agent and a local one satisfy the same interface — .name and .run(text) -> str:

from samcoadk import RemoteAgent

billing = RemoteAgent.discover("http://billing:8000")
reply = billing.run("what is invoice 42?")

Moving an agent out to its own service is invisible to its caller.

A handler that cannot continue raises Clarify, which the server reports as the A2A input-required state rather than a failure — the difference between "this agent is broken" and "this agent is waiting for you".

Two dialects, one method

message/send carries parts, and a part is either prose or a JSON object:

client.send("what is invoice 42?")                  # TextPart — a conversation
client.send(data={"op": "screen", "index": "NIFTY"})  # DataPart — a contract

Text is what you want when the answer is for a person and the caller cannot know the question in advance. Data is what you want between two programs that have agreed on fields: it needs no model to interpret it, so a peer can answer deterministically and prove it did.

from samcoadk.a2a import data_of, status_data, text_of

task = client.send(data={"op": "screen", "index": "NIFTY 50"})
result = data_of(task)          # the completed task's artifact DataPart

data_of is strict where text_of is forgiving, and deliberately: a thin text reply is a thin answer, but a caller that cannot tell "the screener is down" from "the screen matched nothing" will present an unresolved universe as a resolved one. So every way it can be wrong raises, and the exception says which. A paused task carries its payload on the status message instead — read it with status_data.

On the server, a DataPart goes through its own door:

def handle(payload: dict) -> dict:
    return {"matched": screen(payload["index"])}

app = create_app(runner, data_handler=handle)

The model still answers text. It never sees the DataPart — feeding a wire contract to a model asks it to re-derive what the payload already states, and makes a deterministic call cost a token budget and a round of nondeterminism. Without a data_handler configured, a DataPart is refused rather than silently stringified into a prompt.

Tests

pip install -e ".[test]"
pytest -q

Database tests skip silently without a server, so a bare green run does not mean they executed:

SAMCOADK_TEST_MONGO=mongodb://localhost:27017 \
SAMCOADK_TEST_MYSQL='mysql://user:pw@localhost/samcoadk_test' pytest -q

Coming from 0.2.0

0.2.0 0.3.0
agent.run(text, history=session.history) runner.run(user_id=…, session_id=…, message=…)
Agent(...).history gone — the transcript is Session.events
ConversationHistory Session.events + session.as_messages()
samcoadk.sessions.InMemorySessionStore InMemorySessionService
samcoadk.mongo.MongoSessionStore DatabaseSessionService("mongodb://…")
samcoadk.Guardrails, keyword_blocklist, … your own callbacks (see above)
samcoadk.runtime.* (capabilities, contracts, boot) gone — build the agent in Python
boot.load(__file__, policy=…, models=…) Agent(...) and Runner(...)
samcoadk.a2a.registry.AgentRegistry RemoteAgent.discover(url)
samcoadk.console.create_app samcoadk.server.create_app(runner)
Agent(model="openai/gpt-4.1") + gateway env Agent(model="gateway/openai/gpt-4.1") — explicit
samcoadk.ops.* (progress, prometheus, budget) gone — Metrics/Tracer remain in observability

The gateway is no longer selected by an environment variable being set. Say gateway/ and pass the URL, or the same model string means different things in different shells.

Metadata

Release files for samcoadk 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for samcoadk 0.4.0
File Size Uploaded
samcoadk-0.4.0.tar.gz 77.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for samcoadk 0.4.0
File Interpreter ABI Platform
samcoadk-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 137.7 kB

Release files / samcoadk-0.4.0.tar.gz

Download URL samcoadk-0.4.0.tar.gz
Size 77.0 kB
Tags Source
SHA-256 checksum
How to use checksums
22f18f4a423bd85b2e860b6c79cf65473a3782e266016be59f3b3fb6ffcd7cc5
BLAKE2b-256 checksum
How to use checksums
be1deecb24617a468af35d76d2b22abd8e4535bf83694b45e74e0bc3979546f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / samcoadk-0.4.0-py3-none-any.whl

Download URL samcoadk-0.4.0-py3-none-any.whl
Size 60.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c2fb781e3207d4c4f5a25299b2bd6b1cec306007c7e48ee14c7ac0fd97413d26
BLAKE2b-256 checksum
How to use checksums
b74d9c085e180bf956567d0ea45c3a29d962a7cd03f65044624aaa05802a4a25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release 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