AWS Builder Center
Knows when to speak: a 2B model decides when a meeting agent wakes the LLM

Knows when to speak: a 2B model decides when a meeting agent wakes the LLM

A local Strands Decider screens every line of a meeting in about 370ms and wakes an LLM on Amazon Bedrock only when something happens. On 16 held-out meetings, the LLM saw one line in four, used 4× fewer tokens, and interrupted wrongly 3.4× less often than an LLM reading every line.

The problem: meeting bots talk too much, or not at all

Put an AI assistant in a meeting and you get one of two bots. One stays silent and sends a summary afterwards, when it's too late to fix the wrong number someone just quoted. The other comments on everything: every sentence wakes the model, every reply costs tokens and a second or two, and soon people mute it.
A good facilitator does neither. They stay quiet almost all of the time and speak up only when it matters:
  • someone commits to a task ("I'll send the deck to legal") but gives no date
  • the group makes a decision that should be on the record
  • someone quotes a number that contradicts the pre-read ("crash-free is like 97%", but the fact sheet says 99.2%)
  • a question is left hanging and the meeting moves on
  • the talk drifts off the agenda
Knows When to Speak is a meeting agent built to behave like that facilitator. It's for team leads and project managers who run many recurring meetings and want help without a bot that talks over them.

What it does


In the screenshot, Tom says crash-free sessions were "like ninety-seven percent". Decider scores "figure to check" at 0.72, past the wake threshold. The LLM checks the fact sheet and the agent says "Quick check: the pre-read says 99.2%, not 97%." Two lines later Raj quotes the right number. That wakes the LLM too, and it correctly stays quiet.
On every line of the transcript, the agent asks Strands Decider a handful of yes/no questions about the latest lines in one call. A small policy turns the probabilities into one of three behaviors:
Tier
When
What happens
LLM tokens
Speak
confidence above the category's  speak  threshold
the Strands agent on Bedrock is woken, told which category fired and how confident the gate is, and acts through its tools
yes
Note
between  note  and  speak 
a silent margin card for the recap
none
Silent
below  note 
nothing
none
When it wakes, the LLM does what Decider can't: it compares a figure with the fact sheet, writes the action item down with its owner and date, or composes a 20-word line to say out loud. A cooldown keeps the agent to one spoken line a minute. A confirmed contradiction of the fact sheet is the one exception.

The decision we handed to Decider

"Should I speak now?" is asked on every line, dozens of times a minute, and most of the time the answer is no. That shape suits a decision model better than an LLM:
  • It's frequent and local. Each question is about the latest one or two lines: did someone commit, decide, quote a number, ask something? A 2B model handles local judgements like these well, and answers all of them in one forward pass.
  • It returns calibrated probabilities, not prose. Confidence maps straight onto behavior: speak, note quietly, or stay silent. Thresholds become product settings ("how annoying are we allowed to be?") instead of prompt tweaks.
  • Latency has to be flat. On every line, a 5-second tail means the agent comments on something three lines ago. Decider runs locally at 366ms p50 / 433ms p95 on an RTX 3060.
  • Most answers are "no", and those shouldn't cost anything. With the gate in front, a quiet line costs one local forward pass and zero LLM tokens.
What we left to the LLM is what LLMs do well: checking a claim against a document, wording a tactful correction, and writing the recap.

Architecture

The gate is wired in as a Strands  InterventionHandler . On a quiet line it denies the model call before it happens, so no tokens are spent. On a woken line it adds a note telling the agent which category fired and with what confidence.
On AWS. The repo's  infra/  folder is one Python CDK stack. A single g6.xlarge (NVIDIA L4), from the Deep Learning AMI, runs the demo app and Decider as two systemd services, with Decider bound to  127.0.0.1 . The instance role may invoke only the configured Bedrock model, and the security group opens the app port to one IP. There is no SSH: you get a shell through SSM Session Manager. A CloudWatch alarm stops the instance after an idle hour, and  cdk destroy  removes all of it. We deployed, tested and tore it down this way; first boot takes about three minutes.

The reusable pattern:  interjection_policy 

The gate lives in its own folder,   patterns/interjection_policy/  , with no meeting code in it. It fits any agent that watches a stream and should mostly stay quiet: Slack channels, support chats, log tails, game commentary.
Python
gate = InterjectionGate(DeciderClient(),
make_interjection_questions({"bug_report": "a report of a software bug or outage"}),
TieredPolicy({"bug_report": Thresholds(speak=0.8, note=0.5)}))
agent = Agent(model=..., interventions=[InterjectionIntervention(gate, render_state=lambda _: window.render())])
agent(new_chunk) # denied with no LLM call unless the gate says SPEAK
Questions. One yes/no question per category, each with a  true  and a  false  description that names the hard negative ("no commitment, or only a vague suggestion"). Using one question per category rather than one big choice matters because events co-occur: "Okay, June 3rd, and I'll tell marketing" is a decision and an action item.
Policy. Per-category  speak  and  note  thresholds, a cooldown, dedupe against what's already flagged, and streaks ("off topic for 3 lines in a row"). Categories can opt out of the cooldown or dedupe; record-keeping categories do, because a silent card shouldn't be blocked by a spoken one.
Low-confidence handling. The NOTE tier is the answer to "what happens when the model isn't sure?". A borderline action item becomes a silent card the recap can still use. It costs no tokens and doesn't interrupt anyone.
Thresholds are config, tuned on your own labelled windows with  scripts/tune_thresholds.py . The pattern's README has a "When NOT to use this" section. The short version: skip it when every message needs a reply, or when the judgement needs long-range memory.

Results

We needed meetings with ground truth, so we wrote them: 48 scripted meetings across eight themes (launch go/no-go, sprint planning, incident postmortem, PTA, wedding planning, and others). Each has planted events, planted near-misses ("someone should probably look at that" is not a commitment), personas with their own agendas, and an independent audit pass. Labels come by construction. Thresholds and question wording were tuned on the dev split (2,125 windows); agent results are on 16 held-out test meetings (1,123 lines, 86 minutes).
The LLM throughout is  gpt-oss-20b  on Amazon Bedrock. We picked it to keep a pet project's cost to a few dollars, and Claude Haiku 5.5 wasn't enabled on our account at the time. The token counts are priced at Claude list prices below.

1. The gate: Decider vs an LLM asked the same questions

Question (F1)
Decider 2B, local
gpt-oss-20b, Bedrock
qwen3:8b, local ¹
Action item
0.37
0.56
0.18
Decision made
0.30
0.47
0.25
Figure claim
0.66
0.79
0.60
Open question
0.41
0.84
0.65
Fell for action-item traps
0 of 44
18%
14 of 14
Fell for decision traps
3% of 68
9%
33%
Latency p50 / p95
366 / 433 ms
1,201 / 5,729 ms
2,564 / 3,212 ms
¹ First 600 dev windows.
We'll say it plainly: a capable LLM detects more.  gpt-oss-20b  beats Decider on every question's F1. What Decider brings is everything around detection:
  • A flat latency tail. Its p95 is 70 ms above its p50. The Bedrock model's p95 is 5.7 s.
  • It doesn't take the bait. Decider flagged none of the 44 planted fake commitments. An agent that tells someone "I've noted that as your action item" when they didn't commit is worse than one that misses it.
  • It runs locally and offline, with no API dependency, no noisy neighbours and no per-call cost.
That makes Decider a better gate than a judge: let it screen everything cheaply, and let the LLM do the reasoning on the few lines that pass.

2. The agent: with and without the gate

The same Strands agent ran over the test meetings twice, with the same tools, prompt, cooldown and model. In the gated run, Decider chooses which lines wake the LLM. In the ungated run, every line wakes it, and the LLM decides for itself whether anything happened.

Decider gates the LLM
LLM reads every line
Lines sent to the LLM
27%
100%
LLM input / output tokens
0.58M / 26k
2.31M / 87k
Unjustified spoken lines per meeting-hour
7.7
25.8
Action items (precision / recall)
0.64 / 0.43
0.39 / 0.83
Decisions
0.39 / 0.61
0.52 / 0.24
Wrong figures corrected
0.86 / 0.51
0.89 / 0.81
Off-topic drift caught
0.22 / 0.35
0 / 0
An LLM that sees every line finds something to say about most of them. The gate cuts unjustified interruptions by 3.4×. It's a trade, not a free win: the LLM reading everything catches more action items and wrong figures, while the gated agent catches more decisions and is the only one that notices drift.

3. Tokenomics

The same token counts at Bedrock on-demand list prices (us-east-2, global cross-region, from the AWS Pricing API; we paid only the first row):
Model (input / output per M tokens)
Per meeting-hour, gated
Ungated
gpt-oss-20b ($0.07 / $0.30)
$0.03
$0.13
Claude Haiku 5.5 ($0.10 / $0.50)
$0.05
$0.19
Claude Sonnet 5.5 ($2 / $10)
$0.99
$3.84
Claude Opus 5.5 ($4 / $20)
$1.98
$7.68
"Use the cheapest model that's good enough" is a fair answer when the model is  gpt-oss-20b . It stops being enough once you want a stronger model, or an agent that carries more context than our 1–2k-token prompt: the whole transcript, the documents, memory. The cost of reading every line grows with that context. Decider's cost stays at one local forward pass.

4. On AWS

Deployed with the repo's CDK stack on a g6.xlarge (NVIDIA L4) in us-east-2, the Decider answered the same 2,125 dev windows in 163 ms p50 / 184 ms p95, 2.2× faster than the laptop. Accuracy matched the laptop run to within 0.01 F1 on every question. Replaying a full meeting through the deployed app, the Decider took 154 / 170 ms per line, and  gpt-oss-20b  took 448 / 996 ms on the lines it was woken for.
Do you need the GPU? On the same instance's 4 CPU cores with CPU-only PyTorch, one Decider call took about 15 seconds, roughly 90× slower. A meeting produces a line every few seconds, so yes.

What surprised us, and what failed

  • A general LLM out-detects Decider. We expected the reverse and planned the article around it. The honest result changed the pitch from "more accurate" to "fast, steady, local, hard to fool, and cheap to run on every line".
  • How you ask matters more than thresholds. Off-topic drift was one option in an "which agenda item is this?" choice, and Decider almost never picked it: recall 0.02. Asked directly ("Are the latest lines an off-topic tangent rather than meeting business?"), recall on dev went to 0.66.
  • Calibration isn't uniform. Decider is under-confident on "has the pending question been answered?". The best cut on dev was 0.3, not 0.5, which raised F1 from 0.54 to 0.83 and stopped the agent nagging about questions that had been answered.
  • Sometimes a regex wins. Decider's "open question" fired on statements as well as questions (precision 0.36). Requiring a "?" on the line raised precision to 0.93 with the same recall. We kept both: Decider for whether it's a real question, the "?" as a cheap check.
  • The window remembers, and so does the gate. A figure stays in the rolling window for several lines, and Decider kept flagging it on each one. Most of its false alarms were echoes of an event already handled. The fix: a figure must appear on the latest line to wake the fact check.
  • The cooldown ate the corrections. Fact corrections were being swallowed by the speech cooldown after a weaker nudge had just used it up. A confirmed contradiction now overrides the cooldown, once per fact. Figure-correction recall on a pilot meeting went from 0 to 5 of 5.
  • Small LLMs copy their prompt. A local  qwen3:8b  agent said "Mei's question about screenshots" in a meeting with no Mei, because that was the prompt's example. We switched to placeholders.
  • We were 50× wrong about tokens for a day. Strands'  accumulated_usage  is a running total across an agent's life, and we added it on every wake. Watch for this if you reuse a Strands agent across turns.
  •  gpt-oss-20b  has quirks. It leaked  <|channel|>  markup into tool names, sometimes answered a forced tool call with plain text or an empty reply, and once printed a non-breaking hyphen that crashed a Windows console. Each needed a small guard.
  • Still unsolved: "nobody answered that". Nudging an unanswered question works poorly with or without the gate (precision under 0.2). Telling a dropped question from one answered three lines later, or parked on purpose, needs more context than a short window gives.

How to reuse it

Bash
git clone https://github.com/agentic-systems-ai/strands-decider-when-to-speak.git && cd strands-decider-when-to-speak
pip install -e ".[decider]"
strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 --port 8099 --device cuda
python patterns/interjection_policy/example.py # a 30-line Slack lurker
Copy  patterns/interjection_policy/  and  decider_client.py  into your project, define your categories as yes/no questions with a sharp  false  description, label about 100 windows from your own stream, and tune the thresholds. Full results and caveats:   RESULTS.md  .

References

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article