Skip to main content

welt-io-strands

pypi python

The Strands Agents (Python) adapter for Welt's wire contract.

Install

uv add welt-io-strands

Usage

See examples/agent — the smallest complete agent built on this package (text streaming, tool use, image generation, file output, file input, and human-approval tools), which doubles as the example for Welt's Quick Start. The sections below explain the adapters it wires in.

API

The wire between Welt and the agent is JSON, specified by Welt's wire contract — plain Strands values do not fit it in either direction. Two functions adapt the inbound payload, three the outbound stream.

Inbound

decode_messages(messages)

Returns a copy of Welt's Converse-shaped messages with the base64-encoded file bytes restored to the raw bytes Strands expects; everything else — the format token included — is carried over untouched, and the input is left alone. Its in-place predecessor, decode_file_blocks(messages), is deprecated.

decode_interrupt_responses(responses)

Turns Welt's resume payload — a mapping of interrupt id to the answer a human chose — into the interruptResponse items that Agent.stream_async resumes from.

Payloads that violate the contract

Both functions check the payload against Welt's published schema, vendored into this repository as schema/, and raise jsonschema.exceptions.ValidationError on one that fails — naming the path that broke it, down to the block:

$[1].content[0].image.source.bytes: '' should be non-empty

Welt does not send those, so a raise means the caller is not Welt or Welt has a bug; either way, decoding what is left would hand the agent a conversation with a turn missing.

The schema is the whole of what the adapter checks. The one thing it annotates without asserting is that the file bytes are base64, which decode_messages finds out by decoding them: a string that is not raises binascii.Error.

Outbound

renderable_events(events, agent=..., files_from=...)

Reduces raw stream_async events — not JSON-serializable as-is — to the events Welt renders:

Strands emits On the wire In the Slack thread
Text deltas data The streamed reply
Tool invocations and results current_tool_use / tool_result "Using tool" indicators (tool output stays off the wire)
Image / document / video blocks the model produces, or a tool named in files_from returns file An uploaded file (size limits)
Pending interrupts interrupt Buttons and/or a text field

A run that stops for human input ends its stream with one interrupt event per pending interrupt; agents that do not use interrupts see no change.

A tool hands files to the model for either of two reasons — to have it read them, or to give them to the human — and only the agent knows which is which, so name the tools whose files belong in the thread:

async for event in renderable_events(
    stream, agent=agent, files_from={"generate_image"}
):

A tool left out keeps its files to the model: strands-tools' file_read reading a PDF does not drop it into the thread as a side effect. A tool named there needs no code of its own — strands-tools' generate_image returns the image as a tool-result block, and naming it is all it takes; a tool of your own returns image, document, or video blocks the same way. The agent is what makes the names resolvable: its messages hold the tool behind each result, the only place that survives a resume, where the stream carries the result alone.

Uploaded names come from the block — a document's own name plus its format, the block's kind for the rest (image.png). That name is the model's handle on the document as much as a filename, and Converse rejects a request whose messages carry two documents under one name, so a tool that returns documents has to keep their names apart across the run: strands-tools' file_read appends a short uuid to each.

file_event(name, data)

Builds the same file event from a filename and raw bytes, for the files the host app attaches itself:

yield file_event("report.csv", csv_bytes)

Tools have no use for it — they hand files to the agent as content blocks, and files_from decides which of those reach the thread.

interrupt_reason(message, options=..., input=...)

Builds the structured reason Welt renders as a message with the specified widgets — choice buttons (options), a free-text field (input), or both. The specs are the wire's own shapes; omitted fields keep Welt's defaults, and the reason is checked against Welt's schema before it is returned, so a typo raises here instead of reaching the thread as Welt's default rendering:

answer = tool_context.interrupt(
    "deploy-approval",
    reason=interrupt_reason(
        "Deploy to prod?",
        [
            {"value": "y", "label": "Deploy", "style": "primary"},
            {"value": "n", "label": "Cancel"},
        ],
        input={"label": "Or type your answer"},
    ),
)

Working with interrupts

Welt's Interrupts doc covers the Slack side: how each reason renders, who can answer, multiple questions, and expiry. On the Strands side:

  • Prefix your interrupt names (myapp-deploy-approval). Hook-raised interrupts must be unique across the whole event, tool-raised ones within their tool — a prefix keeps both as the agent grows.
  • Strands' ready-made HumanInTheLoop intervention works over Welt as-is. Its string reasons render with Welt's default Approve / Deny buttons, whose y / n values its default evaluator understands. Do not pass ask: both of its inline modes block the agent waiting for input that Slack can never deliver — the default interrupt/resume mode is the one Welt drives.
  • Route stdio consent prompts through interrupts instead. For strands-tools packages that gate themselves behind a stdio prompt, set BYPASS_TOOL_CONSENT=true and let HumanInTheLoop do the gating over Slack. The strands-tools handoff_to_user tool is likewise stdio-bound; a small interrupt-raising tool of your own is the replacement.
  • Code before interrupt runs again on resume. Strands re-executes the interrupted tool from its start, so whatever precedes an interrupt and must not run twice — side effects, or work that must match what the human approved — has to be skipped on the second pass. Memoizing on tool_context.tool_use["toolUseId"], the same id on both passes, is enough: the cache lives in the same process as the interrupt state it pairs with. The example agent's sample_draft_report shows the pattern.

Supported Versions

Welt releases first; welt-io-strands follows, mirroring the minor version. While both are 0.x, a welt-io-strands 0.Y release supports Welt v0.Y — other combinations may work, but come with no guarantee.

License

MIT

Download files

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

Source Distribution

welt_io_strands-0.5.0.tar.gz (24.7 kB view details)

Uploaded Source

Built Distribution

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

welt_io_strands-0.5.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file welt_io_strands-0.5.0.tar.gz.

File metadata

  • Download URL: welt_io_strands-0.5.0.tar.gz
  • Upload date:
  • Size: 24.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for welt_io_strands-0.5.0.tar.gz
Algorithm Hash digest
SHA256 83e861e4e3fdfaff4371d5ae98a081956aca0d5f331fdabc1a1bbdca07d31202
MD5 106aa6a2b409985a38419288bef34168
BLAKE2b-256 0179d49ad92b54e903ee26e7881ade0f9890466e4472cb8ff923f9c44caa0624

See more details on using hashes here.

File details

Details for the file welt_io_strands-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: welt_io_strands-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for welt_io_strands-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c8e46d999d8dc918d954f525fa1a91abddcd4ba88e840e0e7a831e1f4ae74d7c
MD5 6de86f1c79f261fc592abf92ac5bf49a
BLAKE2b-256 5a1f55a84390f3387d7ed6d64cb681ca069eb88688c40c72b86b026b7b64b53d

See more details on using hashes here.

Release history Release notifications | RSS feed

0.10.1

2 files

0.10.0

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.3

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