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.

Built With

Share this project:

Updates

Submission history