Inspiration
Everyone has a version of this story: a $180 monitor sits in its box for three weeks, the return window quietly closes, and the money is just gone. Multiply that across every retailer you buy from — Amazon's 30 days, Target's 90, Best Buy's 15 — and it's real money lost to nothing more than forgetting a date. When we saw the Agents for Humans brief asking for an agent that handles a genuinely repetitive piece of life admin, this was the one that felt both universally relatable and small enough to build end-to-end in the time we had: ReturnPilot, an agent that watches your purchases so you never lose money to an expired return window.
What it does
Every day, ReturnPilot's heartbeat wakes up and:
- Ingests new receipt emails — extracting product, merchant, price, purchase date, and return deadline (from the receipt itself when stated, or computed from the merchant's policy otherwise).
- Checks deadlines across everything it's tracking.
- Decides, per item:
- Already marked for return and cheap? It just handles it — starts the return and schedules a follow-up to confirm the refund posts.
- Expensive or ambiguous? It stops and asks — one clear approval request, not a silent decision on your behalf.
- Remembers. Once you tell it (or it learns from a pattern) that you prefer Best Buy returns in-store, it uses that automatically next time, without asking again.
The before/after is the whole pitch: before, "I forgot to return it and lost $179." After, "ReturnPilot found the receipt, noticed the deadline, prepared the return, and asked me once."
How we built it
ReturnPilot is built on the AWS Strands Agents SDK, running Claude on
Amazon Bedrock as the reasoning model. The agent has 11 tools —
scan_receipts, extract_purchase, lookup_return_policy, save_purchase,
check_deadlines, get_preference, remember_preference,
request_human_approval, initiate_return, create_calendar_reminder, and
update_purchase_state — and a system prompt that encodes the exact decision
policy: routine items get handled automatically, anything over a price
threshold or with ambiguous intent gets gated behind a human approval step.
Memory (purchases + learned preferences) lives behind a storage abstraction that runs on DynamoDB in production and falls back to local SQLite for fast iteration — same interface, swappable with one environment variable. The whole thing is fronted by a Streamlit dashboard: an Ingest button, a "Simulate Daily Run" button standing in for the production EventBridge trigger, a live Pending Approval card with Approve/Keep buttons, and a Memory panel that makes the "it actually remembers" claim visible rather than just asserted.
We deployed the dashboard publicly on Streamlit Community Cloud, with AWS credentials stored in Streamlit's encrypted Secrets — never in the repo, never in an env file — so anyone can open the live link and watch a real Bedrock-backed agent reason through the same three purchases in real time.
A deterministic policy-fallback layer (same decision logic, no LLM calls) also backs the dashboard, so the app degrades gracefully to "same decisions, non-LLM" rather than breaking outright if Bedrock access is ever unavailable — and doubles as a fast, free smoke test we could run before every deploy.
Challenges we ran into
- Model access gating. The newest Claude model on Bedrock
(
claude-sonnet-5) threwAccessDeniedExceptionfor a freshly provisioned account — not every model is available to every account by default. We fell back through the catalog until Claude Sonnet 4.6 worked, which turned out to be plenty capable for this agent's reasoning needs. region_name+boto_sessionconflict. Passing an explicit AWS session alongside a region string toBedrockModelthrows — the session already carries its own region. A quick fix, but the kind of thing that costs real minutes on a deadline day.- Credential exposure, caught in time. Our first pass at letting the
dashboard read AWS credentials from Streamlit secrets rendered them into a
type="password"input — which still ships the real value to the browser DOM, reveal-icon and all. We caught it before real traffic hit the app and rewrote it so secret values never populate a client-facing widget at all; a locked status message shows instead, with credentials read purely server-side. - Racing the clock. Most of this was built same-day, working backward from a hard submission deadline — which meant leaning hard on mocked inputs (receipt emails, merchant return APIs) to keep the agent's actual reasoning the star of the demo, rather than burning hours on OAuth flows that don't change what the agent is deciding.
What's next
Real Gmail/Outlook ingestion instead of the mocked inbox, real merchant
return APIs instead of the stubbed initiate_return, a live EventBridge
schedule instead of the dashboard's manual trigger, and a full AgentCore
deployment for production hosting.
Built With
- agentic-ai
- ai-agents
- amazon-bedrock
- amazon-eventbridge
- amazon-web-services
- anthropic-claude
- aws-agentcore
- aws-iam
- aws-strands-agents-sdk
- boto3
- dynamodb
- generative-ai
- git
- github
- llm
- python
- sqlite
- streamlit
- streamlit-community-cloud
Log in or sign up for Devpost to join the conversation.