Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

async-combinators-strands-example

Hardening Strands agent model calls with async-combinators.

Strands' only built-in resilience is a bespoke retry mechanism that retries throttling errors only; there is no timeout and no rate limiting at the model layer. This example wraps a Strands OpenAIModel with the stream combinators withRateLimit and withTimeout, and drives Strands' retry loop with the promise combinator withRetry through a modelCallDriver extension point added to the framework (in neu-se/harness-sdk, branch adopt-async-combinators) — a small, targeted change that lets an application opt into combinator-driven retry while preserving Strands' native per-attempt observability: its BeforeModelCallEvent/AfterModelCallEvent hooks still fire once per attempt, not once per call.

What this is

A small interactive "geography & math" agent (src/Agent.ts), with a handful of tools unrelated to the case study (country lookup, unit conversion, arithmetic, file read/write, shell commands). The part that matters is src/ResilientOpenAIModel.ts: a subclass of Strands' OpenAIModel that composes withRateLimit and withTimeout around stream(), plus a resilientRetryDriver that supplies withRetry as the agent's modelCallDriver.

Setup

npm install     # also builds the harness-sdk fork -- see below
cp .env.example .env
# edit .env and set OPENROUTER_LLM_API_KEY (get one at https://openrouter.ai/keys)
npm run build
node --env-file=.env dist/Agent.js

@strands-agents/sdk is installed directly from the fork (github:neu-se/harness-sdk#adopt-async-combinators). Because that's a private npm workspace monorepo, not a publishable package, a postinstall script builds the strands-ts workspace after install and reshapes it in place so @strands-agents/sdk resolves normally — no local sibling checkout required.

Example session

The [LOG] lines are added by this example, not part of Strands, to make the combinators' activity visible: which ones are enabled at startup, and every model call as it happens.

$ npm start

> async-combinators-strands-example@1.0.0 start
> node dist/Agent.js

[LOG] withRateLimit enabled: intervalMs=500
[LOG] withTimeout enabled: timeoutMs=15000
[LOG] resilientRetryDriver created: maxAttempts=3
Geography & Math Agent ready. Type your question or "exit" to quit.

You: How large is France compared to the UK and Spain?
[LOG] ResilientOpenAIModel.stream() called at 2026-08-12T23:52:30.794Z
I'll look up the areas of France, the UK, and Spain so I can compare them.
  ⏳ get_country_info

🔧 Tool #1: get_country_info
✓ Tool completed
[LOG] ResilientOpenAIModel.stream() called at 2026-08-12T23:52:32.548Z
Now I have all three countries' information. Let me compare France's size to the UK and Spain:
  ⏳ calculate

🔧 Tool #2: calculate
✓ Tool completed
[LOG] ResilientOpenAIModel.stream() called at 2026-08-12T23:52:34.262Z
  ⏳ calculate

🔧 Tool #3: calculate
✓ Tool completed
[LOG] ResilientOpenAIModel.stream() called at 2026-08-12T23:52:35.182Z
Here's how France compares to the UK and Spain:

**France's Area: 643,801 km²**
- **UK's Area: 243,610 km²**
- **Spain's Area: 505,990 km²**

**Comparisons:**
- **France vs UK:** France is about **2.64 times larger** than the UK
- **France vs Spain:** France is about **1.27 times larger** than Spain

So France is significantly larger than the UK (more than 2.5 times), but only
moderately larger than Spain. Spain is actually quite large — it's about 78%
the size of France.

You: exit
Bye!

Each [LOG] ResilientOpenAIModel.stream() called line is one model turn: withRateLimit paces the start of each by at least intervalMs, withTimeout bounds each to timeoutMs, and resilientRetryDriver retries — visibly, via its own [LOG] line — a call that throttles or times out.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages