Inspiration
I built mida for a problem I kept hitting myself.
I follow hackathons, grants, fellowships, jobs, research, and crypto. I kept checking Devpost, X, GitHub, company blogs, job boards, and community channels because I didn't want to miss something that mattered.
The information was out there. My problem was spotting what fit me, early enough to act on it.
Most recommendation services build a profile you can't inspect, correct, or delete. I wanted an agent that knows what I'm working toward, keeps looking while I'm busy, and interrupts me when something needs a decision. It also had to show me what it knows about me and why it thinks a link fits.
That agent is mida.
What It Does
mida is a private opportunity agent that lives in Telegram. I built it for people who have to find their own next break: builders entering hackathons, grant and fellowship seekers, researchers, and job hunters without a network sending them leads.
It looks while you're away
- Where it looks: Every six hours, mida reads a curated set of hackathon, fellowship, program, job, and event pages, including Devpost's listings. It adds any sites you've asked it to watch, and searches the web using goals you've confirmed.
- What it sends: It scores what it finds and stays quiet unless a result clears the bar. A strong match (ACT) arrives as its own Telegram message, up to three per run. Possible matches (WATCH) arrive together in one digest. mida keeps the rest to itself.
- When you want it now: Linking your chat starts the first look straight away, and
/findasks for another, once an hour.
It asks you to decide
mida sends messages like:
- "This looks like a strong fit. Want to act on it?"
- "Want me to keep watching this site?"
- "Should I remember this about you?"
It saves nothing about you until you confirm.
It shows its working
Send mida a link and you get a score out of 100, a tier, and reasons that each name the memory or project card behind them.
Tell it something new, confirm it, and send the same link again. If the score moves, mida names the memory that moved it and by how many points, and those points add up to the new score.
You control what it knows
/memorylists your saved memories./forgetdeletes one./correctfixes one.
mida can also build project cards from your public GitHub, flag an opportunity that repeats something you've already built, search the web when you ask, and read the details off a flyer or screenshot.
How I Built It
mida runs as a Strands Agents agent on Amazon Bedrock AgentCore Runtime. I gave the model the work that needs judgment: reading pages, weighing relevance, holding a conversation. Plain code handles anything that needs a guarantee.
The agent
mida starts a fresh Strands Agent for each chat turn. Its tools arrive bound to the signed-in person, so the model can't pick whose memory or state it reads.
Claude Sonnet 4.6 handles conversation and images. Amazon Titan turns text into embeddings that mida compares with your goals, preferences, profile, and projects. To spot repeats, mida matches phrases from your project cards against the page.
Strands features doing real work
- Hooks block any save without a typed yes, and stop a turn after 10 tool calls.
- Skills load detailed instructions for a task when that task comes up.
- A steering plugin runs Claude Haiku to compare each final reply with the tools that ran, so mida can't tell you it saved, searched, or deleted something it didn't.
Memory
AgentCore Memory holds conversation context and long-term semantic memory. Chat reaches it through an AgentCore Gateway, where a Cedar policy lets a signed-in person read their own memory and no one else's.
DynamoDB holds the state you can inspect: confirmed memories, project cards, score records, watched sources, and the /find cooldown. The list /memory shows you is that data, with no hidden profile behind it.
Background discovery
An EventBridge rule wakes a Lambda relay every six hours, and the relay runs one discovery pass per person. Telegram messages reach the same relay through API Gateway. The relay maps each chat to a Cognito identity before it calls the agent runtime.
Firecrawl runs web searches and reads pages that block plain HTTP fetches. Its key lives in SSM Parameter Store. I deploy the stack with AWS CDK and the AgentCore CLI.
How mida Stays Predictable
An agent that runs unattended needs rules it can't talk its way around, so I put the decisions that matter in code the model can't override.
In chat
- Code answers
/memory,/forget,/correct,/watch,/unwatch, and/sourcesbefore the model sees the message. - The chat model, the reply checker, and the image reader run at temperature 0, the setting that makes a model's output least random.
- A hook refuses a save unless your last message starts with a yes. Proposals you don't confirm expire.
- A hook ends a turn at 10 tool calls, and the reply checker gets one retry per turn at most.
In scoring
- Pasted links and background finds go through the same scoring pipeline.
- Fixed weights combine four measures: personal relevance, technical fit, past patterns, and timing, plus a hard eligibility check.
- When two records tie, mida cites the same one for the same inputs.
- If a reason cites a record that doesn't exist, mida drops the reason and moves the result to
NEEDS_HUMAN. - An inferred memory can raise a score but can't change a tier by itself. When it would, the result goes to
NEEDS_HUMAN. - If the score changes don't add up to the new score, mida shows no breakdown.
In the background
A fixed table decides what mida sends:
- Up to three ACT messages.
- One WATCH digest of up to five.
- One search shortlist of up to five.
- Silence for PASS.
- No message when a run finds nothing.
Additional safeguards:
- Discovery skips links it has already scored, and offers a search hit at most once per 30 days.
- Search hits wait as suggestions. mida scores one when you paste it back.
/findtakes one conditional database write per chat per hour, and the relay ignores Telegram's re-sends of a message it already handled.- The fetcher accepts public HTTPS pages, follows at most one same-site redirect, and stops at 2 MB or 10 seconds.
- Logs record counts and error types, with no URLs, titles, or personal text.
How the Project Changed Along the Way
My first version of mida scored links you pasted into Telegram and nothing more. I also planned a web app for sign-up and settings, then cut it to get the core loop working. In this version, Telegram is the whole product.
The first real background run humbled me. mida read eight listing pages, pulled out 167 links, and tried eight. All eight were stylesheets, favicons, or login pages.
I stopped designing around ideal pages and built against saved copies of real ones. I rewrote the extractor, added a smoke test for each source, and switched Devpost to its structured listing data.
Search changed too. At first I sent web search results into scoring, which burned credits and attention. Now mida drops social media results and offers the rest as a shortlist you choose from.
Near the end, I noticed the welcome message still called mida a link scorer, even though background discovery had become the part I used most. I rebuilt the product around it: the first scan runs when you link Telegram, the schedule moved from 12 hours to six, and /find lets you ask for a look any time.
Challenges I Ran Into
Deciding what mida may remember
Saving every conversation would bury the useful parts and make a poor privacy model. mida proposes a memory and waits. A typed yes makes it permanent, and unconfirmed proposals expire.
Proving privacy
I tested the Cedar policy with two users on live deployments. Each user was denied the other's memory in both directions.
Background discovery runs without a signed-in user, so Cedar can't check it. That path relies on the code keeping people apart, and I say so in the README.
Staying quiet
An agent that gets credit for sending things turns into another notification feed. mida sends a message when a result clears the table's threshold, and a run that finds nothing sends nothing.
Keeping the model honest
I moved the rules I cared about into code: hooks for saves, arithmetic checks on score explanations, citation checks against real records, and a second model comparing each reply with the tool log.
Accomplishments I'm Proud Of
Tests cover the behavior I care about most. You score a link, confirm a new preference, and score it again. The test checks that the explanation names the right memory and that the score changes add up.
- 1,400+ tests that run offline, with no AWS account and no network.
- 95% of live chat replies under 2.96 seconds, across 20 calls to the runtime.
- Background discovery running every six hours, each pass finishing in 20 to 30 seconds.
- A memory you can list, correct, and delete.
- Cedar isolation tested live across two users.
What I Learned
My bottleneck was attention. I had more information than I could read, and the useful system is the one that knows me well enough to stay quiet.
I also learned to keep the LLM on a short leash. Models handle ambiguous pages and personal relevance well. Plain software handles permissions, thresholds, state, deduplication, and guarantees better, and mida needs both.
One product lesson surprised me: I trust a memory I can list, delete, and correct more than a smarter memory I can't see.
What's Next for mida
Self-serve sign-up
Today I set up each new user from my laptop. Next comes a small web flow that reads your GitHub, lets you approve your first project cards, sets spending limits, and hands you a link into Telegram.
Outcome tracking
mida judges whether something looks relevant. Next, it should record what happened after: applied, interviewed, rejected, accepted, ignored, or won.
Those outcomes would teach it more than stated preferences do. It could also draft a tailored résumé or cover letter in the background, with you deciding whether to send it.
More sources
Direct adapters for Greenhouse, Lever, and Ashby, a cheap filter before scoring, and checks that a posting is still open. Later, selected public signals from X, Reddit, and LinkedIn.
Stronger background isolation
I want scheduled discovery to get the same policy-enforced isolation that chat has.
Long term, I want mida to act as a private intelligence layer: it learns where you're trying to go, watches while you're busy, and shows you the small share of what it finds that can move you forward.
Log in or sign up for Devpost to join the conversation.