🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 kürzlich 6 Min Lesezeit
0

Human-in-the-Loop for Solon ReActAgent: Pause Risky Tools Before They Run

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Most teams can get an AI agent to call tools. The hard part starts when the tool can move money, delete data, or send irreversible messages.



That is where Human-in-the-Loop (HITL) stops being a slide-deck concept and becomes a product requirement. Solon AI ships this as a first-class interceptor on ReActAgent: the agent reasons and acts as usual, but sensitive tool calls can be paused, reviewed, corrected, and resumed without rebuilding the whole conversation.



This post walks through a production-shaped pattern using only official Solon APIs (current docs against Solon v4.0.3).






Why HITL belongs in the agent loop



A naive design puts approval outside the agent:




  1. agent finishes

  2. your service notices a risky side effect

  3. you try to roll it back



That is usually too late. Solon places HITL on the ReAct lifecycle itself:





  • on Action: before a tool runs, decide whether to interrupt


  • on Observation: after a tool runs, inject human comments back into the agent’s next thought



So the break point is exact: the model has already chosen transfer(amount=2000), but the tool has not executed yet.






The four building blocks



From the official HITL docs, the model is intentionally small:




























Piece Role
HITLInterceptor Declares which tools need review
HITLTask Snapshot of tool name + args for the approver UI
HITLDecision Approve / reject / skip + optional arg fixes
HITL Business-layer helpers: getPendingTask, submit, approve, reject


You do not invent a custom implements Tool interface or wrap business agents in a fictional harness. Domain tools stay as AbsToolProvider + @ToolMapping.






Step 1: Domain tools the agent can actually use






CODE
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.annotation.Param;
import org.noear.solon.ai.chat.tool.AbsToolProvider;

public class FinanceTools extends AbsToolProvider {

@ToolMapping(description = "Query account balance by user id")
public String get_balance(@Param(description = "User id") String userId) {
return "{\"userId\":\"" + userId + "\",\"balance\":5200.00,\"currency\":\"CNY\"}";
}

@ToolMapping(description = "Transfer money to another account")
public String transfer(
@Param(description = "Source user id") String fromUserId,
@Param(description = "Target account") String toAccount,
@Param(description = "Amount") double amount) {
return "Transfer accepted: " + amount + " -> " + toAccount;
}
}






This matches the official domain-tool style used in the e-commerce after-sales sample: provider class + annotated methods, then defaultToolAdd(...).






Step 2: Register sensitive tools with HITLInterceptor






CODE
import org.noear.solon.ai.agent.react.ReActAgent;
import org.noear.solon.ai.agent.react.intercept.HITLInterceptor;
import org.noear.solon.ai.chat.ChatModel;

ChatModel chatModel = LlmUtil.getChatModel();

HITLInterceptor hitl = new HITLInterceptor()
// always pause these tools
.onSensitiveTool("transfer")
// or pause only when the amount crosses a threshold
.onTool("transfer", (trace, args) -> {
double amount = Double.parseDouble(args.get("amount").toString());
return amount > 1000 ? "Large transfer needs human approval" : null;
});

ReActAgent agent = ReActAgent.of(chatModel)
.defaultToolAdd(new FinanceTools())
.defaultInterceptorAdd(hitl)
.maxTurns(10)
.build();






Two useful patterns from the docs:





  1. onSensitiveTool(...) — any call is pending


  2. onTool(name, strategy) — return a reason string to interrupt, or null to let it pass



That keeps low-risk automation fast while still fencing the dangerous path.






Step 3: Expose ask / task / approve over HTTP






CODE
import org.noear.solon.annotation.*;
import org.noear.solon.ai.agent.AgentSession;
import org.noear.solon.ai.agent.react.ReActAgent;
import org.noear.solon.ai.agent.react.ReActResponse;
import org.noear.solon.ai.agent.react.intercept.*;
import org.noear.solon.ai.agent.session.InMemoryAgentSession;
import org.noear.solon.core.handle.Result;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Controller
@Mapping("/ai/hitl")
public class HitlWebController {

private final Map<String, AgentSession> sessions = new ConcurrentHashMap<>();

private final ReActAgent agent = ReActAgent.of(LlmUtil.getChatModel())
.defaultToolAdd(new FinanceTools())
.defaultInterceptorAdd(new HITLInterceptor()
.onTool("transfer", (trace, args) -> {
double amount = Double.parseDouble(args.get("amount").toString());
return amount > 1000 ? "Large transfer approval" : null;
}))
.build();

private AgentSession sessionOf(String sid) {
return sessions.computeIfAbsent(sid, InMemoryAgentSession::of);
}

@Post
@Mapping("ask")
public Result ask(String sid, String prompt) throws Throwable {
AgentSession session = sessionOf(sid);
ReActResponse resp = agent.prompt(prompt).session(session).call();

if (resp.getTrace().isPending()) {
return Result.failure("REQUIRED_APPROVAL", HITL.getPendingTask(session));
}
return Result.succeed(resp.getContent());
}

@Get
@Mapping("task")
public HITLTask task(String sid) {
return HITL.getPendingTask(sessionOf(sid));
}

@Post
@Mapping("approve")
public Result approve(String sid, String action, @Body Map<String, Object> modifiedArgs)
throws Throwable {
AgentSession session = sessionOf(sid);
HITLTask task = HITL.getPendingTask(session);
if (task == null) {
return Result.failure("No pending task");
}

HITLDecision decision;
if ("approve".equals(action)) {
decision = HITLDecision.approve().comment("Verified by operator");
if (modifiedArgs != null && !modifiedArgs.isEmpty()) {
decision.modifiedArgs(modifiedArgs);
}
} else {
decision = HITLDecision.reject("Rejected by operator");
}

HITL.submit(session, task.getToolName(), decision);

// resume from the breakpoint; no need to resend the original prompt
ReActResponse resp = agent.prompt().session(session).call();
return Result.succeed(resp.getContent());
}
}






This is the production shape:





  1. ask runs the agent

  2. if resp.getTrace().isPending(), return the HITLTask to your admin UI


  3. approve writes a HITLDecision

  4. call agent.prompt().session(session).call() again to continue






The closed loop in plain language



Imagine the user says: “Transfer 2000 to account A10086.”





  1. Trigger — ReAct plans, then tries transfer(...). The interceptor stores a HITLTask and interrupts.


  2. Review — an operator opens the pending task, sees tool name + args.


  3. Decide


    • approve as-is: HITL.approve(session, "transfer")

    • reject: HITL.reject(session, "transfer", "account anomaly")

    • fix args, then approve:






CODE
HITL.submit(session, "transfer",
HITLDecision.approve()
.modifiedArgs(Map.of("toAccount", "correct_888")));








  1. Resume — the next call() consumes the decision, runs (or skips) the tool, and the agent produces the final answer from a real observation.






Design notes that matter in production






1) Put HITL on the worker, not the narrator



If you later compose agents with TeamAgent, keep the interceptor on the ReActAgent that actually owns the sensitive tool. Team collaboration can suspend through the same pending mechanism, but the tool fence still lives at the acting agent.






2) Session storage is part of the product



InMemoryAgentSession is perfect for demos. For multi-instance deployments, swap in a shared AgentSession implementation so pending tasks survive restarts and stick to the same conversation id.






3) Prefer threshold policies over “approve everything”



onSensitiveTool is blunt and safe. onTool with a predicate keeps small automated refunds / transfers flowing while still catching the expensive mistakes.






4) Don’t fake business ROI numbers



HITL’s value is operational: fewer irreversible mistakes, auditable breakpoints, human correction of bad tool args. Keep claims qualitative unless you have your own measured baseline.






Minimal checklist before you ship




  • [ ] Tools are AbsToolProvider + @ToolMapping

  • [ ] Risky tools registered via HITLInterceptor

  • [ ] API returns pending tasks when trace.isPending()

  • [ ] Approvals go through HITL.submit / approve / reject

  • [ ] Resume uses the same AgentSession without replaying the user prompt

  • [ ] Session backend matches your deployment topology






Where to read next



Official docs used for this article:





If you are building agents that only chat, you may not need HITL. The moment your agent can spend money or change production state, you do.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hackers Just Poisoned the Rust Supply Chain | Threat Wire
1 Quelle
Hackers Found a Way Into Humanoid Robots | Threat Wire
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Human-in-the-Loop for Solon ReActAgent: Pause Risky Tools Before They Run

Thematisch verwandte Begriffe: HumanintheLoop, Solon, ReActAgent, Pause · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...