Skip to content

API reference ​

Every public name of the SDK, grouped by module. Signatures are those of the released package; the descriptions point to the page that explains the behaviour in context.

The package exports the everyday names from zettcode.sdk. Everything else is in a submodule, which is just as light to import.

ModuleHolds
zettcode.sdkClient, Session, RunStream, prompt, prompt_sync, RunResult, Usage, Image, PlainCommand and the two results a plain command answers with, the common events and handlers, three errors
zettcode.sdk.clientClient, Sessions, PluginReport, prompt, prompt_sync
zettcode.sdk.sessionSession, RunStream
zettcode.sdk.eventsevery event, AnyEvent
zettcode.sdk.typesthe values methods return
zettcode.sdk.handlersapproval rules and handler types
zettcode.sdk.modelslist_models, add_model, update_model, remove_model
zettcode.sdk.errorsevery error

zettcode.sdk ​

The names importable from the package itself:

python
from zettcode.sdk import (
    CacheMode,
    Client,
    Session,
    RunStream,
    prompt,
    prompt_sync,
    RunResult,
    PlainCommand,
    PlainCommandContext,
    TextCommandResult,
    TurnCommandResult,
    Usage,
    Image,
    TextDelta,
    ReasoningDelta,
    ToolStarted,
    ToolFinished,
    ServerToolStarted,
    ServerToolFinished,
    ApprovalRequested,
    QuestionAsked,
    Finished,
    approve_all,
    approve_commands,
    deny_all,
    add_model,
    list_models,
    update_model,
    remove_model,
    ZettCodeError,
    ConfigurationError,
    RunError,
)

They are loaded on first use, so importing the package costs next to nothing. dir(zettcode.sdk) lists them without loading anything.

zettcode.sdk.client ​

prompt ​

python
async def prompt(
    text,
    /,
    *,
    workspace=None,
    config=None,
    config_path=None,
    models=None,
    model=None,
    effort=None,
    permission=None,
    resume=None,
    images=(),
    mentions=False,
    on_approval=None,
    on_question=None,
    on_event=None,
    timeout=None,
    cache=None,
    **settings,
) -> RunResult: ...

Makes a client, runs one request, closes the client, and returns the result. Takes the arguments of Client and of Client.run. See Running prompts.

prompt_sync ​

python
def prompt_sync(text, /, **options) -> RunResult: ...

The blocking form of prompt, for code that is not async. Raises RuntimeError when called from inside a running event loop.

Client ​

python
class Client:
    def __init__(
        self,
        workspace=None,
        *,
        config=None,
        config_path=None,
        models=None,
        model=None,
        effort=None,
        permission=None,
        resume=None,
        **settings,
    ) -> None: ...

The agent for one workspace. An async context manager. Every configuration field is a keyword argument of its own, named and typed (store=, max_iterations=, sessions_enabled=, … — see Settings); a name that is not one is a TypeError.

RaisesWhen
ConfigurationErrorThe configuration, or a model, effort or permission chosen from it, is not usable.
WorkspaceConflictErrorAnother open client works in a different directory.
DaemonStartErrorA background agent was started and never became reachable.
SendErrorsend could not reach anyone; reason says why.
SessionNotFoundErrorresume names a session that cannot be read.

Properties ​

PropertyTypeMeaning
workspacePathThe resolved workspace directory.
configZettCodeConfigThe configuration in force.
closedboolWhether aclose() has run.
sessionSessionThe current conversation.
sessionsSessionsThe stored conversations.
modelstuple[ModelInfo, ...]The configured models; the active one is marked.
modelModelInfoThe active model.
effortstrThe reasoning level the next run uses.
effortstuple[str, ...]The levels the active model accepts.
permissionstrThe shell permission level.
permissionstuple[str, ...]("deny", "ask", "safe", "full").
harnessboolWhether the just-in-time harness is on.
usageUsageWhat the current session has spent.
pluginsPluginReportThe plugins that loaded, every one a switch can reach (entries), and the failures.
self_compactionboolWhether the model may compact this session itself.
self_compaction_choicebool | NoneThis session's own choice, or None when it follows the configuration.

Methods ​

MethodReturnsDoes
prompt(prompt, *, images, mentions, instructions, on_approval, on_question, on_event, timeout, raw, keep_events, defer_answers, cache)RunStreamSends a prompt on the current session. Await the result for a RunResult; iterate it for events.
stream(...)RunStreamThe same method as prompt.
run(prompt, *, images, mentions, instructions, on_approval, on_question, on_event, timeout, keep_events, cache)RunResult (awaitable)await client.prompt(...) as a call. Has no raw.
use_model(name)ModelInfoChooses the model; takes a name or a ModelInfo. A name is looked for as a display name first, then as a model id; each must name one model (ConfigurationError otherwise).
config_filePathThe configuration file the models are read from and written to.
model_entries()tuple[tuple[ModelInfo, Mapping], ...]Each model the file lists with the keys the file writes for it, in file order (a token left to an environment variable is absent).
add_model(entry)ModelInfoAdds a model to the file and to this client; the model in use does not change.
update_model(which, changes)ModelInfoChanges a model by name or by its place in model_entries(); a None value removes the key.
remove_model(which)ModelInfoRemoves a model; returns the one in use afterwards. The only model cannot go.
use_effort(name)strChooses the reasoning level.
use_compact_percent(percent)tuple[ModelInfo, str | None]Moves the compaction point (a share of the window, above 0 and at most 100) from the next request on, and rewrites the configuration file. The second value says why the file was not written, or is None.
use_permission(name, *, remember=True)strChooses the permission level; applies from the next shell command. remember=False leaves the session's remembered level alone, for a choice that covers this run only.
mentions(query="", *, limit=50)tuple[MentionInfo, ...]The @ resources matching a query, for a completion menu. With sessions_enabled=True, the other running sessions come first, as they do in the terminal.
set_plugin_enabled(name, enabled)PluginEntryRecords whether the next start does one plugin's work, and returns it as the list now shows it. An installed plugin and a compiled-in one both write the program's configuration file (config_file): an installed one in [plugins] disable, a compiled-in one in its own key. A config_path= a client was built with is read, not written by this call. Nothing loads or unloads in this process. Raises ValueError when the name cannot be switched.
await join_sessions(on_message, *, direct=False)boolJoin the session broker, so other ZettCode sessions can find this one and send it messages. on_message is called with each IncomingMessage and returns a Receipt; False means sessions are switched off for this client. direct=True says this client runs a message with IncomingMessage.direct set as its reader's own prompt (see send_back).
await send(to, text, *, direct=False, mode="steer")DeliveredSends a message to another session by handle or id, as the reader's own words; needs join_sessions. mode says what a busy recipient does with it: "steer" (the default) hands it to the request that is running, and "followup" waits until that request ends. With direct=True, a session that can (Peer.direct) runs the text as its own prompt and sends its final answer back; for any other the flag is ignored (Delivered.direct says what happened). Raises zettcode.sdk.errors.SendError (its reason says why) when nobody could be reached.
await send_back(message, text)Delivered | NoneSends the final answer of a turn that ran a direct message back to its sender, as this session's agent. A sender that has gone is not an error: None says it was not sent.
reachableboolWhether the session broker is connected right now.
handlestrThe name the other sessions call this one by, or "" before it is registered.
peers()tuple[Peer, ...]The other sessions registered with the broker, from memory (nothing is asked).
watch_peers(callback)—Calls callback() (no arguments) whenever the peers change or the broker is lost or found; None stops the calls. Read peers() and reachable for the state.
send_limitsSendLimitsWhat the broker accepts from a sender, so a front end can refuse before it sends.
watch(session_id), unwatch(session_id)—Ask to be told, as it happens, what another session's turns say; kept across a lost connection. A session streams only while someone watches it.
watch_streams(callback)—Calls callback(StreamEvent) on the event loop for every event of a watched session's turn; None stops the calls. What is streamed is a view, not a record: pieces can be missed, and the stored conversation is what is true once the turn ends.
relay(message, *, on_approval, on_question, on_event, timeout, raw, keep_events, defer_answers)RunStreamRuns one turn for a message another session sent, framed with who it came from.
await usage_ledger()WorkspaceUsageWhat every stored session of the workspace spent, by session, model and day.
await session_stats(ids=None)tuple[SessionStatsInfo, ...]Stored totals of some sessions, or of the current one.
self_compaction_defaultboolWhat a session that has made no choice does ([compaction] self).
use_self_compaction_default(enabled)PathWrites that default into the configuration file and applies it now; raises ConfigurationError when the file is missing or cannot be edited.
use_self_compaction(choice)boolLet the model compact this session itself (True), stop it (False), or follow the default (None).
await check_models(*, timeout=30)tuple[ModelCheck, ...]Ask every configured model one short question, as zettcode doctor.
aclose()—Stops a run in progress, leaves the broker and releases everything. Safe to call twice.

use_model, use_effort and use_permission raise ConfigurationError for a choice that does not exist, with the valid ones in the message.

Sessions ​

client.sessions.

MethodReturnsDoes
await list(*, limit=20)list[SessionInfo]Stored sessions, most recently active first.
new()SessionStarts an empty conversation and makes it current.
await open(session_id)SessionContinues a stored conversation and makes it current.
await latest()Session | NoneContinues the most recently active one.
await read(session_id)StoredSessionReads a stored conversation without making it current: the expensive half of open. Safe beside a run.
read_blocking(session_id)StoredSessionThe same on the calling thread, for code that is already on a worker.
install(stored)SessionMakes a StoredSession current: the cheap half of open. Raises SessionBusyError during a run.

StoredSession(id, title, model, usage, records): records is the parsed file for a front end that draws the conversation itself; its type belongs to the engine, so do not depend on its shape.

PluginReport ​

Frozen dataclass: loaded: tuple[str, ...], failures: tuple[str, ...].

zettcode.sdk.models ​

The configuration file's models, for setting up a machine before there is a client. See Models before there is a client.

python
def list_models(path=None) -> tuple[ModelInfo, ...]: ...
def add_model(entry, *, path=None) -> tuple[ModelInfo, ...]: ...
def update_model(name, changes, *, path=None) -> tuple[ModelInfo, ...]: ...
def remove_model(name, *, path=None) -> tuple[ModelInfo, ...]: ...

entry and changes are the file's keys (model, token, display_model, base_url, anthropic, responses_api, chat, multimodal, temperature, context_window, compact_percent, reasoning_efforts). path defaults to $ZETTCODE_CONFIG or ~/.zettcode/config.toml. Every call returns the whole list, with no model marked active, and refuses a file it cannot read back; the last model cannot be removed. In changes, a key whose value is None is removed from the entry: {"temperature": None} goes back to the provider's own, and {"anthropic": None, "chat": True} moves a model to chat completions without leaving anthropic = false behind.

zettcode.sdk.session ​

Session ​

A handle on one stored conversation. Obtain it from client.session, client.sessions, or Session.fork().

Properties ​

PropertyTypeMeaning
idstrThe session's identity.
titlestr | NoneIts name, or None until it has one.
usageUsageWhat this session has spent in total.
lockedboolWhether another process has it open.
retake()boolTries again to take the lock of a conversation another process had open; True once this client holds it.
tasks()tuple[TaskInfo, ...]The plan the model keeps for this session; TaskInfo(status, content), status being pending, in_progress or completed.
pathPathThe file the conversation is stored in.
currentboolWhether the next run would use it without switching.

Running ​

MethodReturns
prompt(prompt, *, images, mentions, instructions, on_approval, on_question, on_event, timeout, raw, keep_events, defer_answers, cache)RunStream
stream(...)RunStream (same method)
await run(prompt, *, images, mentions, instructions, on_approval, on_question, on_event, timeout, keep_events)RunResult
await ask(question, *, on_event=None, timeout=None, cache="read")str
ask_stream(question, *, on_event=None, timeout=None, cache="read")RunStream
steer(prompt, *, images=(), mentions=False)str | None
await resumable()ResumeInfo | None
resume_stream(*, on_approval, on_question, on_event, timeout, raw, keep_events, defer_answers)RunStream
await resume(*, on_approval, on_question, on_event, timeout)RunResult

cache is "read_write" (the default), "read" or "off"; a side question starts at "read", because its answer is never replayed and is not worth writing into the cache. See CacheMode in zettcode.sdk.types.

prompt raises, at the call: ValueError (empty prompt, bad timeout), TypeError (a piece that is not text or Image), ConfigurationError (images for a model that cannot read them), SessionBusyError, ClientClosedError. Awaiting or iterating the run raises RunError, RunTimeoutError, SessionLockedError, a ValueError for a cache word the engine does not know (checked when the run starts), and whatever a handler raises.

Working on the conversation ​

MethodReturnsRaises
await history()list[Message]SessionNotFoundError
await rename(title)strValueError, SessionLockedError, SessionBusyError
await name_it()str | None—
await fork()SessionSessionNotFoundError, SessionBusyError
await export(path=None)PathValueError when nothing is stored
await context()ContextReport | None—
await compact()Compaction | NoneNothingToDoError, SessionLockedError, SessionBusyError

See Sessions.

RunStream ​

One run. Returned by prompt, stream and resume_stream.

MemberMeaning
await runTakes the rest of the events and returns the RunResult.
async for event in runYields the events as they happen.
async with runCloses the run on leaving the block.
await run.aclose()Stops the run and frees the client. Safe to call twice.
run.resultThe RunResult, or None until the run has ended on its own.

zettcode.sdk.events ​

Every event is a frozen dataclass deriving from Event(session_id: str).

ClassFields beyond session_id
TextDeltatext
ReasoningDeltatext
ReasoningStarted—
ReasoningFinished—
ToolStartedcall_id, name, arguments
ToolFinishedcall_id, name, output, ok, skipped=False
ServerToolStartedcall_id, name, arguments — a tool the endpoint runs, such as a web search
ServerToolFinishedcall_id, name, arguments, output, ok — the query, the pages, and whether it worked
ApprovalRequestedcall_id, command, timeout_seconds=None, remember_supported=False
QuestionAskedcall_id, question, options=(), allow_multiple=False
UsageUpdatedusage
CompactionStarted—
CompactionDeltatext
CompactionFinishedapplied, compaction=None
ModelStoppedreason, message=None
Steeredtext
SteeringFinished—
SteeringInterruptedtext
Noticetext, level
Finishedtext
RawEventevent

AnyEvent is the union of all of them.

ApprovalRequested ​

MemberMeaning
approve(*, remember=False)Let the command run. remember applies only if remember_supported.
deny()Refuse it.
answeredWhether it was settled, by you or the run's handler.
approvedTrue, False, or None while unanswered.

QuestionAsked ​

MemberMeaning
answer(text)Answer it. Raises ValueError for an empty answer.
decline()Tell the model it was cancelled.
answeredWhether it was answered or declined.

The first answer wins. Repeating it is harmless; the opposite raises ValueError. See Approvals & questions.

zettcode.sdk.types ​

All are frozen dataclasses with __slots__, except CacheMode, which is the words a cache mode is written with.

CacheMode ​

"read_write" (the default, and what a turn of the conversation uses), "read" (reuse a warm prefix without marking this request's own content — what a side question uses, since its answer is never replayed) or "off" (no cache directive at all). What a provider can honour is its own: one that caches automatically has no client-side write control, so "read" and "read_write" behave the same there, and "off" only drops a routing key.

Usage ​

input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens (all int, default 0), seconds: float, requests: int, context_tokens: int.

MemberMeaning
total_tokensInput plus output.
cache_hit_rateCached share of the input, or None.
output_rateTokens per second of model time, or None.
since(earlier)What was spent after earlier; counters never below zero.

RunResult ​

FieldType
textstr
session_idstr
usageUsage
toolstuple[ToolRecord, ...]
stopstr | None
stop_messagestr | None
questionstuple[str, ...]
eventstuple[Event, ...]

Properties: ok (stop is None) and failed_tools.

ToolRecord ​

id, name, arguments: dict, output: str, ok: bool, skipped: bool = False.

Image ​

Image(data: bytes, media_type: str) and Image.from_path(path). Media types: image/png, image/jpeg, image/gif, image/webp. Raises ValueError for empty data or another type.

ModelInfo ​

name, model, context_window: int, multimodal: bool, efforts: tuple[str, ...], active: bool = False.

SessionInfo ​

id, title: str or None, created_at: datetime, updated_at: datetime.

Message and ToolCallInfo ​

Message(role, text, tool_calls=(), tool_call_id=None, tool_name=None, ok=True, images=0). ToolCallInfo(id, name, arguments).

MentionInfo ​

token, kind, name, description.

PluginEntry ​

name, origin, builtin, enabled, failure, setting, switchable, why. One plugin as plugins() lists it: what it calls itself, the distribution that carries it (the name a switch is written with, the same as name for a plugin compiled into the program), whether it is compiled in, whether the next start does its work, why it could not load here when it could not, the configuration key that decides a compiled-in plugin ("skills.enabled") — empty for an installed one, which [plugins] disable holds — whether a panel may draw a switch at all, and why it may not when it may not.

IncomingMessage ​

sender_id, sender, text, via, hops, direct, mode. A message another ZettCode session sent: what join_sessions hands its callback, and what relay answers. via is "user" when the sender's reader typed it, "agent" when its agent did, "external" for a program that is not a ZettCode session. mode is "steer" (the default: hand it to the request that is running) or "followup" (wait until that request ends); a front end that cannot steer holds it as a follow-up and answers "queued".

Receipt, Delivered ​

Receipt(accepted, reason="", disposition="") is what the join_sessions callback returns to the sender: whether the message was taken, why not, and whether its turn "started", was "steered" into the running request, is "queued", or was "delivered" to a chat window. Delivered(to, disposition, direct) is what send returns: the handle of the session that took the message, the same disposition, and whether the broker handed it over as the session's own prompt. IncomingMessage.direct is set when the sender is a person's one-to-one chat: run text with session.prompt() and give the final answer back with send_back; the DIRECT_FAILED, DIRECT_UNFINISHED and DIRECT_EMPTY lines in zettcode.sdk.types are what to send back when a turn failed, did not finish, or wrote nothing, and is_direct_notice(text) tells them from an answer.

Peer, SendLimits, StreamEvent, Relay ​

Peer(session_id, handle, title, workspace, status, kind, direct) is another registered session (status is "idle" or "working"; kind is "zettcode" or "external"; direct says it runs a direct message as its own prompt). SendLimits(max_text, rate_limit, rate_window, max_hops) is what the broker accepts. StreamEvent(session_id, kind, text, call_id, name, brief, ok) is one thing a watched turn said: kind is "begin", "text" (more of the answer), "thinking", "tool" (started; name and a one-line brief), "tool_end" (ok), or "end" (what the turn produced is in the stored conversation). Relay(sender_id, sender, via, hops) is Message.relay: who sent a stored user message that came from another session, None for what the reader typed.

DaemonInfo and zettcode.sdk.daemons ​

Background agents are run and listed from zettcode.sdk.daemons: list_daemons() returns DaemonInfo(session_id, workspace, status, name, title, model, effort, permission, pid, started_at) for each one on this machine; await spawn_daemon(...) starts one and returns its DaemonInfo (DaemonStartError when it never became reachable); await stop_daemon(session_id) stops one; daemon_log(session_id, *, lines=200) reads the end of its log.

Totals ​

What a tally in the usage ledger holds: requests, input_tokens, output_tokens, cache_read_tokens, reasoning_tokens (all int), with total_tokens and cache_share (None with no input) worked out.

SessionTotals, ModelTotals, DayTotals, WorkspaceUsage ​

SessionTotals(id, title, started, updated, tokens, models, entries); ModelTotals(model, tokens); DayTotals(day, tokens). WorkspaceUsage(sessions, models, days, total) is what usage_ledger() returns: sessions most recently active first, models busiest first, days oldest first.

SessionStatsInfo ​

id, entries, requests, input_tokens, output_tokens, cache_read_tokens, reasoning_tokens, with total_tokens and cache_share worked out. One row of the terminal's /resume panel.

ModelCheck ​

model, model_id, ok, seconds, detail, input_tokens: int | None, output_tokens: int | None. One model's answer to check_models().

ContextReport and ContextShare ​

ContextReport(window, used, measured_by, shares=(), partial=False, from_provider=False) with properties free and fraction, and measured (what the tokenizer counted, which shares add up to; used may be the provider's own count), estimated (the shares are a characters-per-token guess) and the methods percent(tokens) (a share of the window) and share(tokens) (a share of measured, which is where the context goes). ContextShare(name, tokens, items=0).

Compaction and ResumeInfo ​

Compaction(summary, compressed, kept). ResumeInfo(kind, description).

zettcode.sdk.handlers ​

NameMeaning
approve_all(request)Approves everything.
deny_all(request)Denies everything.
approve_commands(*commands)Returns a handler that approves the named commands and denies the rest. Raises ValueError for a blank rule or one containing a shell character.
ApprovalHandlerCallable[[ApprovalRequested], bool | None | Awaitable[bool | None]]
QuestionHandlerCallable[[QuestionAsked], str | None | Awaitable[str | None]]
SHELL_METAThe characters that stop a command from matching a prefix rule.

zettcode.sdk.errors ​

See Errors & timeouts for the hierarchy and when each is raised.

ClassBasesExtra attributes
ZettCodeErrorException—
ConfigurationErrorZettCodeError, ValueError—
ClientClosedErrorZettCodeError—
SessionBusyErrorZettCodeError—
SessionLockedErrorZettCodeError—
SessionNotFoundErrorZettCodeError, LookupError—
NothingToDoErrorZettCodeError—
WorkspaceConflictErrorZettCodeError—
DaemonStartErrorZettCodeErrorlog
SendErrorZettCodeErrorreason
RunErrorZettCodeErrorretryable, partial_text
RunTimeoutErrorRunError, TimeoutErrorseconds

Types you may want in annotations ​

python
from zettcode.sdk import Client, Session, RunStream, RunResult
from zettcode.sdk.events import AnyEvent
from zettcode.sdk.handlers import ApprovalHandler, QuestionHandler
from zettcode.sdk.types import ModelInfo, SessionInfo, Message, ContextReport

The package ships a py.typed marker, so type checkers read the annotations.

Released under the MIT license.