Inspiration
A friend's fridge stopped overnight. She lives alone, doesn't drive, and had a freezer full of food with a few hours left on it. She asked an AI assistant what to do, and it told her — accurately, at length — how long food keeps.
That's the gap. She wasn't short of information. She was short of somebody to work out who had a freezer, who had a car, whether either of them was free in the next two hours, what to do when the first person said no, and whether the food actually made it.
That work is boring, fiddly, and genuinely hard. It's also exactly what falls through the cracks for people who are on their own, unwell, without transport, or simply out of hours in the day. Everyone says "let me know if you need anything." Almost nobody knows what to do next.
Advice is cheap. Coordination is hard. We wanted to build the thing that does the hard part.
What it does
Lucent takes a problem described in plain language and drives it to a resolution. It doesn't pretend to be the helper — it's the coordinator that makes people and services work together.
Every request becomes a mission: a temporary micro-network of people, tasks, dependencies and approvals, assembled around one problem and torn down when it's resolved. Lucent reads the request, checks its own boundaries, builds a dependency-aware plan, searches for who could help, sends the ask, handles the answer — including no — replans around what it learned, stops for your approval before anything reaches a real person, arranges the specifics, and refuses to close until there's evidence.
The moment that makes it worth building is the recovery. When the first person asked isn't free, Lucent doesn't move to the next name on the list. It asks why, turns the answer into a constraint, and searches again with better information:
✕ Alex is only free this evening, at the weekend, not this afternoon
⟳ WHAT THAT TOLD US — Help has to happen this afternoon
The next search treats this as a hard filter rather than a preference.
✓ Jordan accepted and can be there at 4:30 PM
…also covers the shopping — two tasks could become one trip
That gap is deliberate. The first search deliberately doesn't score availability, because stated availability in any directory is usually stale. The refusal is what earns it a hard filter — and the whole thing is explained on screen while it happens.
How we built it
Seven agents, nine tools, one durable state machine.
Each specialist is a real strands.Agent with its own system prompt and its own
narrow tool set. No agent can do another's job — the Matching Agent
physically cannot contact anyone, because contact_candidate isn't in its tool
list. The Safety and Boundaries Agent has no tools at all: its output is a
classification the orchestrator must respect, never an action it takes.
| Agent | Tools it may call |
|---|---|
| Mission Orchestrator | request_approval, update_task_status |
| Understanding | ask_clarifying_question |
| Safety & Boundaries | none — it can only stop the mission |
| Mission Planner | create_task, update_task_status, replan_mission |
| Matching | search_helpers |
| Coordination | contact_candidate, request_approval, schedule_handoff |
| Follow-Up | request_approval, record_evidence, update_task_status |
Thirteen mission states, and every transition written to SQLite before it reaches a browser. Stop the API mid-mission, restart it, reload the page: the mission is exactly where you left it, because the runtime reads its position from the database rather than from memory. We tested this by killing the process while an approval was pending — approving afterwards drove the mission straight through search, refusal, replan and rematch. That's the difference between a mission and a chat transcript.
The frontend is Next.js with Server-Sent Events carrying a monotonic sequence number, so a mid-mission refresh replays exactly what was missed.
The part we're most pleased with: Lucent runs the complete multi-agent
mission with no AWS account at all. We wrote LocalPolicyModel, a
first-class implementation of the strands.models.Model interface. It speaks
the same Bedrock-shaped streaming protocol, emits real toolUse blocks, and
supports both of Strands' structured-output paths — but instead of calling a
hosted model it asks a deterministic Python policy what the next move should be.
The agent loop, tool registry, JSON schemas, tool execution and structured-output validation are all the genuine Strands machinery in both modes. Only the thing producing the tool calls differs, and swapping them is one function. Set AWS credentials and the same agents run against Bedrock instead.
Lucent never overclaims which one you're looking at — the engine label is in the footer, in Judge View, and stamped on every persisted agent trace.
Challenges we ran into
Making failure reproducible without faking it. A demo that only sometimes shows the replan is worthless, but a scripted one is dishonest. We settled on a rule stated openly in the UI: the first request a mission sends is declined, every request after it is accepted. Deterministic, never random, and it means failure handling is exercised on every run, for any request a judge types in.
Time made the demo unreproducible. Rankings and offered times depend on what
part of the day it is, so the same mission told a different story at 9am and 8pm.
LUCENT_DEMO_WINDOW pins the clock, which is what "reproducible every time"
actually requires.
A constraint that was silently discarded. The extracted time window was being written to the mission, then overwritten by the scratchpad on the next save — which quietly killed the entire learn-a-constraint story. It failed by producing a plausible run, which is the worst kind of bug.
Recording the film. The demo video is a real recorded session, not a
slideshow, and getting there meant fixing four things that all failed silently:
innerText returns text as rendered, so uppercase-styled labels never matched
sentence-case waits; the screencast tags its output with a nominal frame rate and
the picture played back 15% slow; navigating away from the running app stops the
capture delivering frames while the encoder keeps writing; and a spotlight that
doesn't track its target strands a bright rectangle over unrelated content.
Accomplishments that we're proud of
- It refuses to be the answer when it shouldn't be. Describe something that sounds like an emergency and Lucent does not create a mission — the Safety Agent stops before planning and the screen gives escalation steps instead.
- It won't close on optimism. Answer "not yet" at the final check and it stays open and asks again. Silence is never treated as success.
- It's honest about what's simulated. Every helper profile is fictional and labelled as such in the timeline, on the approval card and in the API.
- The whole thing runs from a clean clone with no credentials, and the headless script walks the full lifecycle and asserts it completes.
What we learned
That the interesting part of an agent isn't the happy path — it's what it does with no. A system that treats a refusal as information rather than an error gets to be genuinely useful; one that retries the same thing is just a loop.
Also that approval gates are a feature, not friction. Knowing that nothing reaches a real person without a decision is what makes a coordination agent something you'd actually let near your neighbours.
What's next for Lucent
Real provider adapters behind the two interfaces that are currently simulated —
a volunteer-network API in place of the seeded directory, SMS or email in place
of the outreach adapter. Both are Protocols already; nothing in the agent layer
changes. Then multiple concurrent missions, a household or street shared
workspace, and an AgentCore deployment of the same agent definitions.

Log in or sign up for Devpost to join the conversation.