
CodeAhimsa: a coding tutor that refuses to hand over the answer, guarded by Strands Decider 2B
A Socratic coding tutor where Amazon Bedrock writes the replies and Strands Decider 2B, running locally, blocks any reply that would spoil the exercise, at zero Bedrock tokens per check
The problem
When I’m stuck on a programming assignment, an AI assistant usually just gives me the whole answer. I copy it, the assignment passes, and I learn nothing. Prompting a tutor to “only give hints” helps, but a prompt is a request, not a rule. One frustrated “just give me the full code", and a helpful model hands it over anyway.
I wanted a tutor whose rule is enforced in code. That is CodeAhimsa (ahimsa means “do no harm”; here, no harm to learning).
Demo video (2:54): https://youtu.be/l1YLZODMKss
Code (Apache-2.0): https://github.com/Pratheeksh-7S/codeahimsa
Code (Apache-2.0): https://github.com/Pratheeksh-7S/codeahimsa
What the agent does
CodeAhimsa is a Strands agent that tutors beginners in Python, Java, SQL and C. Amazon Bedrock (Nova Lite) writes every reply. The tutor is never allowed to answer in plain text. It has to deliver each reply through one tool. That gives us a single checkpoint every reply must pass through before a student sees it.
The decision Decider 2B makes
Before
send_to_student runs, Strands Decider 2B answers two typed yes/no questions about the draft reply:- spoils_solution: Does the reply contain a complete, ready-to-paste solution instead of a hint? Block if p ≥ 0.60.
- Grounded: Is the reply grounded in what the student actually asked? Block if p < 0.30.
The state decider sees just two lines: the student’s message and the tutor’s draft. The decider runs locally on my laptop’s CPU, with no GPU.
How it is wired
A strand
InterventionHandler called SocraticGuardrail hooks before_tool_call. For every send_to_student call, it asks Decider both questions, then picks one of four verdicts:- Proceed when both checks pass.
- Guide (hint) when the draft spoils the solution. The model is told to rewrite it as a hint or a guiding question, with no full code.
- Guide (on topic) when the draft drifts from the question.
- Fallback after two failed rewrites. A
Transformswaps the message for a safe Socratic reply, so the guardrail fails closed, never open.
Seeing it work
A well-prompted Nova Lite rarely spoils the answer on its own. To show the guardrail doing its job, the app has an “eager” switch that swaps in a plain helpful-assistant prompt. With the ear on and the guard off, asking “just give me the full code” puts a ready-to-paste solution on screen. With the guard on, the same request is drafted, scored at 0.92, blocked, rewritten, blocked again at 0.78, and finally delivered as a hint that scores 0.17.
Results
I hand-labelled 16 tutor replies (8 spoilers, 8 hints, across Python, Java, SQL and C) and asked both Decider 2B and a Bedrock LLM judge (Nova Lite, same question, YES/NO) to classify them.
- Accuracy: Decider 16/16, LLM judge 16/16
- Bedrock tokens per check: Decider 0, LLM judge 91 in / 2 out
- Median latency: Decider 2.1 s (CPU), LLM judge 0.9 s
Both were perfect on this set, so the interesting part is elsewhere. Decider’s scores separate cleanly: every spoiler scored 0.76 to 0.90 and every hint 0.27 to 0.49, leaving a wide margin around the 0.60 threshold. And every check costs zero Bedrock tokens, because the decision runs on the student’s own machine.
To be honest about latency: on my CPU, Decider takes about 2.1 s per check, slower than the Bedrock call. The roughly 115 ms figure AWS quotes needs a GPU. For a tutor, where the student is reading and thinking anyway, 2 seconds is acceptable.
The reusable pattern
The guardrail lives in
patterns/socratic_guardrail/ and is not tied to tutoring. It is three small parts: the typed questions with thresholds, a tiny HTTP client, and the InterventionHandler. Any Strands agent that replies through a tool can reuse it. Change the question text and the fallback message, and pass it interventions=[SocraticGuardrail(...)] to your agent. Examples: a homework helper, an interview-practice bot that must not reveal the model answer, or a support agent that must not quote internal pricing.What I learned
- Route replies through a tool. Interventions fire on tool calls, so making the reply itself a tool call is what made the guardrail possible.
- Fail closed. My first version let a spoiler through after the retry limit ran out. The fix was a transform that replaces the message with a safe fallback.
- Calibrate thresholds on real scores. The grounded score was noisy on replies that were on topic, so I lowered its threshold from 0.45 to 0.30 to stop false blocks.
- Small models are enough for narrow decisions. A 2B model answering one well-phrased yes/no question matched a much larger LLM on this task.
#strands-decider-2b #guardrails
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article