Inspiration

Credit card, bank, and store sign-up bonuses are effectively free money, but the rules that gate them are genuinely hostile. Minimum-deposit windows, maintenance periods, once-per-family restrictions, state exclusions, "new customer only" clauses. Miss one deadline and you forfeit the whole bonus. No partial credit.

That's a rules and memory problem, not really a chatbot problem. That made it a good fit for Cognee's persistent memory and Strands Agents' reasoning and tool use.

I built Freebie Monitor, an agent that remembers your situation, finds offers that actually fit it, and tracks the real deadlines. It helps you work through the offers, but it never pretends it can sign up for anything on your behalf.

What it does

You tell it about yourself once. Things like whether you own a home, your travel habits, whether you have idle cash, and whether you're open to new accounts.

It searches current reward offers and checks each one against your situation. It cites the specific fact that makes an offer worth considering or not, and flags the eligibility rules that are easy to miss: what counts as a qualified deposit, geographic restrictions, once-per-X-months exclusions, and so on.

If you decide to pursue an offer, it builds a calendar of things you actually have to do: open the account, hit the deposit target, maintain the balance, and finish before the offer expires. You can check things off or report your progress as you go.

It never opens an account or signs up for anything. That's a hard rule enforced in code, not just a prompt. There is an apply_for_account tool, but a Strands steering handler blocks it before it can execute.

It also checks for open class-action activity against an issuer before recommending it. An existing settlement can make you ineligible for a new bonus.

How I built it

Cognee's cloud API, using add/cognify/search, holds the person's profile and commitments as a knowledge graph. I inject the relevant information into the agent's context before each turn through Strands' MemoryManager.

Strands handles the reasoning and tool orchestration. The tool set is small: find deals, check a company's legal standing, start tracking something, and the deliberately blocked apply_for_account tool.

Bright Data handles live deal discovery and the class-action check. I built a fetch-once/extract-many cache around it. A page gets scraped once, then a small focused extraction pass turns it into structured facts. Asking the same question again doesn't re-scrape the page or run the extraction again.

I started this as a polished web app, then rebuilt the agent core in Python to match the hackathon's required starting point, a Cognee + Strands memory-store pattern. I kept the same MemoryManager/hook/steering shape while pointing it at infrastructure I could actually get working under time pressure.

Challenges I ran into

Nearly every integration had a real, undocumented bug that I had to find by testing rather than reading the docs.

Streaming tool calls broke in opposite ways on different providers. Bedrock's streaming path fragmented tool calls into dozens of empty pieces, which I fixed with streaming=False. Our local model fallback had the opposite problem. streaming=False crashed a completely different way because of a Strands SDK bug where a non-streaming reply gets re-synthesized into delta events and leaks a raw int into a field the accumulator assumes is always a string.

Small models also turned out to be fundamentally unreliable at copying numbers through tool-call arguments. Even when explicitly told to quote a user verbatim, they would turn "$400" into "$4."

The fix was to remove the model from that path entirely. The code reads the raw dollar figure directly from the person's message instead of letting the model retype it.

I also hit Bright Data returning HTTP 200 with an empty body intermittently, which I solved with retries, an unencoded-space bug in my own query URLs, and an AWS Bedrock account that needed a one-time manual "use case" form submitted in the console before it would invoke any model. That happened in the middle of the build.

I also had an honest self-correction. I had wired Docker into the "start tracking" step mostly to check a sponsor requirement. It was running a fixed script that didn't provide any real isolation for what is fundamentally calendar-and-reminder logic. It wasn't doing anything useful, so I don't want to dress it up as an important part of the architecture.

Accomplishments that I'm proud of

Every sponsor integration is real and independently verified, not just wired up and hoped for. I captured raw HTTP responses proving that Cognee's cloud graph, Bright Data's live scrapes, and the model calls all genuinely execute.

I verified the steering block directly rather than just assuming it worked. I called the handler with a synthetic apply_for_account request and confirmed that it returns Guide (blocked) every time, independent of what the model decides to do. That's the actual safety property that matters.

I also had a case where the agent's own memory got corrupted by the number-retyping bug. It caught the inconsistency itself, told the user plainly that the mistake was on me, not them, and cross-checked the authoritative deal data to give the right answer instead of confidently repeating a wrong number.

What I learned

Small local models cannot be trusted to relay structured or numeric data through tool-call arguments. The fix is architectural, not prompt engineering. Structured data needs to stay owned by code, and the model should only handle the smallest piece of free text that actually needs generation.

Streaming behavior is provider-specific and has to be tested per provider. You can't assume that because one provider works, another will behave the same way.

And matching a required starting point is really about matching the integration pattern. In my case, that meant keeping the same MemoryManager/hook/steering shape rather than reproducing infrastructure choices that I couldn't realistically get working under the time constraints.

What's next for Freebie Monitor

Real calendar export with .ics files and email/SMS reminders. Right now the nagging is in-app only.

Dedupe protection so tracking the same deal twice doesn't create duplicate records.

Full live Bright Data discovery replacing the last of my seed data, with richer eligibility extraction for things like state restrictions and once-per-family rules, sourced from real issuer terms pages instead of hand-modeled.

Bedrock as the primary model once the account's use-case review clears. A larger model should eliminate the number-retyping failure mode I had to design around.

And a proper reconciliation between the original web UI and this agent core, so the two aren't running as separate demos.

Built With

Share this project:

Updates

Submission history