Agent bez HITL ljudske odobrenja, usudio bi se čak i rm -rf.
Trenutni naš Agent potpuno veruje AI, što je isto kao da smo AI prepustili i neke opasne radnje.
Na primer, brisanje foldera.

Ali pravi Agent očigledno ne bi trebao da ima takva neograničena dozvole. Kad susretne neke opasne operacije, trebalo bi da kontroonu preda ljudima.
Dakle, HITL, Human-in-the-Loop, ljudsko odobrenje se uključilo.
Pre izvršenja opasne operacije, prvo pitaj.

Ovaj članak će vas voditi kroz implementaciju kompletnog HITL sistema, uključujući prepoznavanje opasne politike, formatiranje zahteva za odobrenje, prikupljanje korisničke interakcije, i najbitniji dizajn sloja presretanja.
01, Zašto Agent treba ljudsko odobrenje
Prvo da pojasnim kontekst.
PaiCLI već ima ReAct, Plan-and-Execute, Memory, RAG, Multi-Agent, funkcije su prilično potpune.
Ali što je moćniji, to je veći rizik.
Agent koji izvršava zadatak može:
koristiti write_file da prepiše fajl koji ne želite da dirate, koristiti execute_command da pokrene naredbu koja nije po očekivanju, koristiti create_project da kreira na disku gomilu foldera o kojima nemate pojma.
Agent sam po sebi nije zlonameran, ali model ponekad "pogrešno razume", vašu nameru iskrivi u nešto drugo.
Naročito u Plan-and-Execute i Multi-Agent režimu, kad period izvršenja zadruga bude dugačak, izvršilac može napraviti veliku grešku.
Polazište HITL-a je vrlo jednostavno: pre izvršenja, neka ljudsko oko baci pogled, potvrdi da nema problema pa nastavi.

Ovo nije nov koncept.
U Claude Code-u postoji parametar --dangerously-skip-permissions, naziv parametra dangerously govori sve — u podrazumevanom stanju, pre izvršenja opasne operacije će stati i čekati vašu potvrdu.
U Codex-u postoji Approval Mode, isti princip.
Dizajneri alata su shvatili da Agent koji ima mogućnost pisanja na disk i izvršavanja naredbi, bez ikakvog ograničenja predobra riskantno.
Halucinacije velikih modela iako opadaju, nikada neće doseći nulu.
Korisničke namere se prevedu u radnje kroz prompt, sama po sebi ima gubitak informacija. Gubitak se nagomila sa halucinacijama, izvršenje može skrenuti. HITL ne znači da Agent nije pouzdan, nego pre toga što model još nije dovoljno pouzdan, čovek mora kontrolisati poslednju liniju odbrane.
02, Kako se sudara da li je operacija opasna
Ovde postoji ključna odluka dizajna: presuda opasne operacije, da li koristiti statička pravila ili dinamičku LLM presudu?
Ja biram statička pravila, razlog je vrlo jednostavan.
Dinamička presuda znači da pre svakog poziva alata mora pitati LLM: "Da li je ova operacija opasna?" To je ne samo sporo, već nepouzdano — danas se kaže da je opasna, sutra možda kaže da je sigurna.
Sam model ima randomnost, koristiti ga da sudara "treba li ljudska intervencija", privremeno nećemo razmatrati.
Direktno upotrebimo statička pravila da zalepimo listu.
public class ApprovalPolicy {
// Skup alata koji zahtevaju ljudsku potvrdu
private static final Set<String> DANGEROUS_TOOLS = Set.of(
"write_file",
"execute_command",
"create_project"
);
public static boolean requiresApproval(String toolName) {
return DANGEROUS_TOOLS.contains(toolName);
}
public static String getDangerLevel(String toolName) {
return switch (toolName) {
case "execute_command" -> "🔴 Visok rizik";
case "write_file" -> "🟡 Srednji rizik";
case "create_project" -> "🟡 Srednji rizik";
default -> "🟢 Sigurno";
};
}
public static String getRiskDescription(String toolName) {
return switch (toolName) {
case "execute_command" -> "Izvršiće Shell naredbu na sistemu, može modifikovati fajlove, instalirati softver ili uticati na stanje sistema";
case "write_file" -> "Upisaće ili prepisaće sadržaj fajla, originalni sadržaj će biti izgubljen";
case "create_project" -> "Kreiraće nove direktorijume i fajlove na disku";
default -> "Sigurna samo-za-čitanje operacija";
};
}
}read_file, list_dir, search_code ova tri alata su samo-za-čitanje operacije, ne menja nijednu stvar, ne treba odobrenje.
write_file, execute_command, create_project će pisati na disk ili pokrenuti naredbu, treba ljudsku potvrdu.
Logika jednostavna, rezultat predvidiv, ovako Agent treba da izgleda.

execute_command je visokog rizika (će pokrenuti shell naredbu), write_file i create_project su srednjeg rizika (će pisati na disk).
Ovaj nivo informacije će kasnije biti prikazan u okviru odobrenja, da korisnik odmah vidi indeks opasnosti trenutne operacije.
03, Kako dizajnirati zahtev za odobrenje
Zahtev za odobrenje je najvidljiviji deo HITL sistema — određuje šta korisnik vidi, a potom da li korisnik može doneti razumnu odluku.
Definicija ApprovalRequest je sledeća:
public record ApprovalRequest(
String toolName,
String arguments,
String dangerLevel,
String riskDescription,
String suggestion,
String callerContext
) {
public static ApprovalRequest of(String toolName, String arguments, String suggestion) {
return new ApprovalRequest(
toolName,
arguments,
ApprovalPolicy.getDangerLevel(toolName),
ApprovalPolicy.getRiskDescription(toolName),
suggestion,
null
);
}
}Record je ovde pogotovo primeran, jer je zahtev za odobrenje čista nosilac podataka, nepromenjiv, ne treba mu logika metoda. Jednom konstruišeš, to je konačno stanje.
Ali samo podataka nije dovoljno, treba još jedan toDisplayText() metod, da formatira ova polja u lepi terminalni izlaz:
┌──────────────────────────────────────────────────────────┐
│ ⚠️ Potrebno odobrenje │
├──────────────────────────────────────────────────────────┤
│ Alat: write_file │
│ Nivo: 🟡 Srednji rizik │
│ Rizik: Upisaće ili prepisaće sadržaj fajla, originalni sadržaj će biti izgubljen │
├──────────────────────────────────────────────────────────┤
│ Parametri: │
│ path: "/Users/demo/project/config.json" │
│ content: "{\"version\": \"2.0\", ...}" (1240 znakova) │
└──────────────────────────────────────────────────────────┘U ovom toDisplayText() metodu postoji jedan detalj vredan pažnje, to je algoritam širine prikaza.
U terminalnom izlazu, kineski znakovi i emoji zauzimaju dve kolone, engleski znakovi zauzimaju jednu kolonu.
Ako se ne obradi, direktno korišćenje dužine stringa za padding, kineski i emoji će izvrnuti desnu ivicu okvira, čitav okvir odobrenja će izgledati kriv.
Dakle ApprovalRequest implementira displayWidth() metod, specijalno računa širinu kolone terminalnog prikaza stringa:
static int displayWidth(String s) {
int w = 0;
int i = 0;
while (i < s.length()) {
int cp = s.codePointAt(i);
i += Character.charCount(cp);
if (isWideCodePoint(cp)) {
w += 2; // CJK / puni ugao / emoji zauzimaju 2 kolone
} else {
w += 1;
}
}
return w;
}CJK znakovi (kinesko-japansko-korejski jedinstveni ideogrami), puni ugao simboli, česti emoji, svi su prepoznati kao široki znakovi, računaju se po 2 kolone.
Bez obzira da li u parametrima ima kineskih staza, kineskih imena fajlova, desna ivica okvira odobrenja uvek je poravnata.
Dodatno, prikaz parametara je urađen sa JSON strukturnom analizom.
Umesto da ceo JSON string nalepimo u okvir, bolje prikazati polje po polje, write_file content obično je dugačak, deo preko 120 znakova se seče sa ... i dodaje ukupna dužina.
04, Kako dizajnirati rezultat odobrenja
Korisnička odluka ima pet vrsta:
public record ApprovalResult(
Decision decision,
String modifiedArguments,
String reason
) {
public enum Decision {
APPROVED, // Odobreno, koristi originalne parametre
APPROVED_ALL, // Odobri sve iste operacije u ovoj sesiji
REJECTED, // Odbijeno, Agent će dobiti razlog odbijanja
MODIFIED, // Izvrši sa izmenjenim parametrima
SKIPPED // Preskoči ovaj korak
}
}Ovih pet odluka pokrivaju većinu scenara.
APPROVED je najobičnija odobrenja, svaki put treba potvrdu.
APPROVED_ALL je pogodnije — ako radite grupu operacija nad fajlovima, ne želite svaki put potvrđivati write_file, pritisnite a i kasnije operacije ovog alata će sve biti odobrene, do sledećeg /clear.
MODIFIED dozvoljava korisniku da izmeni parametre pre odobrenja, na primer Agent hoći da fajl upiše u /tmp/test.json, vi možete promeniti u ~/project/test.json pa izvršiti.
REJECTED i SKIPPED se razlikuju po semantici: odbijanje se nosi sa razlogom da Agent ponovo planira, preskakanje samo ne izvršava ovaj korak.

effectiveArguments() metod kapsulira logiku izbora parametara:
public String effectiveArguments(String originalArguments) {
if (decision == Decision.MODIFIED && modifiedArguments != null
&& !modifiedArguments.isBlank()) {
return modifiedArguments;
}
return originalArguments;
}Ako korisnik bira izmenu, koristi izmenjene parametre; u svim ostalim slučajevima koristi originalne parametre.
05, Kako implementirati terminalnu interakciju
HitlHandler je interfejs obrade odobrenja, TerminalHitlHandler je terminalna implementacija.
Dizajn interfejsa je vrlo jednostavan:
public interface HitlHandler {
boolean isEnabled();
void setEnabled(boolean enabled);
ApprovalResult requestApproval(ApprovalRequest request);
}Jezgro TerminalHitlHandler je metod requestApproval. Ceo proces je dodat synchronized.
U Multi-Agent režimu više Worker-a konkurentno može pokrenuti opasnu operaciju, ako nema sinhronizacije, dva okvira odobrenja će se istovremeno ispisati u stdout, stdin će zauzeti dve niti, korisnik će videti da je terminalni izlaz u neredu.
synchronized garancira da su zahtevi za odobrenje serijski — jednom se prikazuje jedan, čeka se korisnička odluka, pa se sledeći pojavi.
@Override
public synchronized ApprovalResult requestApproval(ApprovalRequest request) {
// Ako je ovaj alat već odobren "sve propusti" u ovoj sesiji, direktno prođi
if (approvedAllTools.contains(request.toolName())) {
out.println(" [HITL] " + request.toolName() + " je već odobren za sve u ovoj sesiji, automatski prolazi");
return ApprovalResult.approveAll();
}
out.println();
out.println("────────── ⚠️ HITL Zahtev za odobrenje ──────────");
out.println(request.toDisplayText());
return promptUntilDecision(request);
}Obratite pažnju da iznad okvira odobrenja ispisujemo jedan separator ────────── ⚠️ HITL Zahtev za odobrenje ──────────.
Ovo je rešenje jednog problema iskustva: kad Agent strimuje "odgovor" sadržaj, Markdown renderer ima bafer.
Ako se okvir odobrenja nalepi uz uzvodni reasoning tekstualni izlaz, vizuelno je teško razlikovati gde se završilo, gde je počelo.
Separator razdviza vizuelni razmak.
Glavna interaktivna petlja promptUntilDecision ima fail-safe dizajn: kontinuirano 5 puta neprepoznat unos, konzervativno se tretira kao REJECTED.
private ApprovalResult promptUntilDecision(ApprovalRequest request) {
for (int attempt = 0; attempt < 5; attempt++) {
out.println("Izaberite operaciju: [y/Enter] Odobri [a] Odobri sve [n] Odbij [s] Preskoči [m] Izmeni parametre");
out.print("> ");
out.flush();
String input = in.readLine();
String normalized = input.trim().toLowerCase();
if (normalized.isEmpty() || normalized.equals("y")) {
return ApprovalResult.approve();
}
switch (normalized) {
case "a" -> { /* Odobri sve */ }
case "n" -> { /* Odbij i prikupi razlog */ }
case "s" -> { return ApprovalResult.skip(); }
case "m" -> { /* Tok izmene parametara */ }
default -> out.println(" ❌ Neprepoznata opcija, unesite y/a/n/s/m");
}
}
// Kontinuirano više puta nevažeće, konzervativno odbij
return ApprovalResult.reject("Kontinuirano više puta nevažeći unos");
}Zašto je odbijanje, a ne podrazumevano odobrenje?
Jer ako korisnik nije eksplicitno izrazio saglasnost, ne treba izvršiti. Ako se zaista desi neka čudna granična situacija, bolje neka Agent ponovo planira.
approvedAllTools koristi ConcurrentHashMap.newKeySet(), sigurno u scenariju više niti.
06, Sloj presretanja: HitlToolRegistry
Ovo je najkritičnija klasa celog HITL sistema.
HitlToolRegistry nasleđuje ToolRegistry, samo prekida executeTool jedan metod:
public class HitlToolRegistry extends ToolRegistry {
private final HitlHandler hitlHandler;
public HitlToolRegistry(HitlHandler hitlHandler) {
super();
this.hitlHandler = hitlHandler;
}
@Override
public String executeTool(String name, String argumentsJson) {
// HITL nije omogućen ili ovaj alat ne zahteva odobrenje, direktno izvrši
if (!hitlHandler.isEnabled() || !ApprovalPolicy.requiresApproval(name)) {
return super.executeTool(name, argumentsJson);
}
// Konstruiši zahtev za odobrenje i pokreni odobrenje
ApprovalRequest request = ApprovalRequest.of(name, argumentsJson, null);
ApprovalResult result = hitlHandler.requestApproval(request);
if (result.isRejected()) {
String reason = result.reason() != null ? result.reason() : "Korisnik je odbio ovu operaciju";
return "[HITL] Operacija je odbijena: " + reason;
}
if (result.isSkipped()) {
return "[HITL] Operacija je preskočena";
}
// Odobreno (uključuje izmenjene parametre)
String effectiveArgs = result.effectiveArguments(argumentsJson);
return super.executeTool(name, effectiveArgs);
}
}Cela logika je ovih 20 linija.
Prvo proveri da li je HITL omogućen, ako nije omogućen direktno super.executeTool(), u potpunosti isti kao obični ToolRegistry.
Proveri da li ovaj alat zahteva odobrenje, ako ne treba direktno propusti. Samo ako su "HITL omogućen + alat opasan" ta dva uslova istovremeno zadovoljena, ide proces odobrenja.

Ako je HITL isključen, hitlHandler.isEnabled() vraća false, prvi if skraćuje, super.executeTool() direktno izvršava.
Trošak presude runtime-a je jedno čitanje boolean + jedno Set traženje, gotovo zanemariv.
Ovo je vrednost nasleđa ovde: nema potrebe uvoditi novi interfejs, nema potrebe menjati stari kod.
07, Integracija u Main.java
HitlToolRegistry se u Main.java uniformno inicijalizuje:
TerminalHitlHandler hitlHandler = new TerminalHitlHandler(false); // Podrazumevano isključeno
HitlToolRegistry toolRegistry = new HitlToolRegistry(hitlHandler);
// Prosledi Agentu za korišćenje
Agent agent = new Agent(apiKey, toolRegistry);Prekidač se kontroliše komandom /hitl:
/hitl on # Uključi
/hitl off # Isključi
/hitl # Prikaz trenutnog stanja
U komandi /clear je dodat još jedan detalj: čisti se nakupljeni zapis "odobri sve" u ovoj sesiji.
case "/clear" -> {
memory.clear();
hitlHandler.clearApprovedAll(); // Obriši APPROVED_ALL zapise
System.out.println("Istorija razgovora je obrisana");
}Ovaj dizajn je veoma potreban.
Korisnik u jednoj sesiji bira "odobri sve" za write_file, sledeći put /clear ponovo počinje razgovor, ako se ne obriše, ovaj zapis odobrenja je još uvek tu. Brisanje istorije razgovora treba i stanje odobrenja zajedno obrisati, semantički su vezani.
08, Tretira se konflikt striming renderer-a
Ovaj problem me je zblokirao neko vreme.
PaiCLI-jev Agent kad strimuje reasoning_content, TerminalMarkdownRenderer ima bafer. Renderer tek kad naiđe na \n flush-uje jedan red sadržaja.
Problem je: između iteracija tool call-a, u renderer-u možda ima još neflush-ani tekst bafer.
Ako se u ovom trenutku HITL okvir odobrenja direktno pojavi, ubaciće se iz sredine polureda teksta, naslov i sadržaj okvira odobrenja će biti pomereni.
Način rešavanja je pre ulaska u tool-call iteraciju, prvo pozvati renderer.resetBetweenIterations(), prisilno flush-uje bafer:
// Agent.java: pre ulaska u izvršenje alata flush rendering bafer
renderer.resetBetweenIterations();
// Zatim izvrši alat
String toolResult = toolRegistry.executeTool(toolName, toolArgs);Agent.java, SubAgent.java, PlanExecuteAgent.java tri puta su sređena. Ako bilo koji nedostaje, u odgovarajućem režimu će se pojaviti nered u tipografiji.

Ovakvi problemi su vrlo skriveni, unit teško pokriva, mora se ručno jednom proći kroz striming Agent razgovor da bi se otkrilo.
09, Nekoliko dizajn kompromisa HITL-a
Postoji nekoliko dizajn odluka vrednih razrade.
Kompromis 1: Podrazumevano isključeno
HITL je podrazumevano isključen, treba ručno /hitl on uključiti.
Možda će svi misliti, zašto zaštitna funkcija nije podrazumevano uključena? Razmatrao sam, ali konačno odlučio podrazumevano isključiti.
Razlog: u fazi razvoja i debug-iranja, česta potvrda odobrenja će prekinuti ritam.

Vi znate šta Agent hoće da radi, verujete ovu operaciju, ali svaki put morate pritisnuti y, jako je naporno. HITL je pogodan scenarij — ne ste sigurni šta Agent radi, ili rezultat operacije je nepovratan, tada ima vrednost.
Podrazumevano isključeno daje korisniku aktivni izbor, ne prisiljava sve da prolaze kroz proces odobrenja.
Kompromis 2: Razlog odbijanja se vraća Agent-u
Kad korisnik pritisne n da odbije, razlog odbijanja se vraća Agent-u kao rezultat poziva alata:
[HITL] Operacija je odbijena: putanja je pogrešna, treba upisati u ~/project a ne /tmp
Agent vidi ovaj rezultat, može prilagoditi misao i ponovo planirati.
Ovo je bolji dizajn od "tiho ne izvršavati" — Agent zna zašto je odbijen, a ne da čeka zbunjen na licu mesta ili beskonačno ponavlja istu operaciju.
Kompromis 3: APPROVED_ALL je opseg na nivou alata, ne globalni
"Odobri sve" se čuva kao ključ po imenu alata, odobrenje write_file ne znači odobrenje execute_command.
Ova granularnost je namerna. Razlika rizika između pisanja fajlova i izvršenja naredbi je velika, korisnik može biti spreman da odobri grupu operacija nad fajlovima, ali izvršenje naredbi želi da potvrđuje jedan po jedan.
Kompromis 4: Izolacija testiranja interfejsa
HitlHandler je interfejs, TerminalHitlHandler je implementacija. U testovima se koristi Mockito-jev mock, ne treba pravi terminal stdin/stdout.
Ovo razdvjanje interfejsa nije za "lepi dizajn patern", nego da bi testovi mogli proći. Terminalna interakcija je vrlo teška za testiranje, jednom u kodu je hardkodirano System.in, test mora simulirati unos tastature, ekstremno teško.
Nakon abstrakcije interfejsa, paket-nivo konstruktor TerminalHitlHandler dozvoljava injekciju prilagođenog BufferedReader i PrintStream, test može direktno servirati string:
// Test kod: injektiraj "y\n" simuliraj korisnički unos y
BufferedReader mockIn = new BufferedReader(new StringReader("y\n"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
TerminalHitlHandler handler = new TerminalHitlHandler(true, mockIn, new PrintStream(out));10, Unit testovi
Tri test klase: ApprovalPolicyTest, ApprovalResultTest, HitlToolRegistryTest.
ApprovalPolicyTest verifikuje tačnost politike — koji alati trebaju odobrenje, koji ne, da li je nivo opasnosti tačan:
@Test
void testRequiresApproval() {
assertTrue(ApprovalPolicy.requiresApproval("write_file"));
assertTrue(ApprovalPolicy.requiresApproval("execute_command"));
assertTrue(ApprovalPolicy.requiresApproval("create_project"));
assertFalse(ApprovalPolicy.requiresApproval("read_file"));
assertFalse(ApprovalPolicy.requiresApproval("list_dir"));
assertFalse(ApprovalPolicy.requiresApproval("search_code"));
}HitlToolRegistryTest je najkritičniji — korišćenjem Mock načina verifikuje logiku presretanja:
@Test
void testDangerousToolIsIntercepted() {
HitlHandler mockHandler = mock(HitlHandler.class);
when(mockHandler.isEnabled()).thenReturn(true);
when(mockHandler.requestApproval(any())).thenReturn(ApprovalResult.approve());
HitlToolRegistry registry = new HitlToolRegistry(mockHandler);
// Izvrši opasan alat
registry.executeTool("write_file", "{\"path\":\"test.txt\",\"content\":\"hello\"}");
// Verifikuj da je odobrenje pozvano jednom
verify(mockHandler, times(1)).requestApproval(any());
}
@Test
void testSafeToolBypassesHitl() {
HitlHandler mockHandler = mock(HitlHandler.class);
when(mockHandler.isEnabled()).thenReturn(true);
HitlToolRegistry registry = new HitlToolRegistry(mockHandler);
registry.executeTool("read_file", "{\"path\":\"test.txt\"}");
// Siguran alat ne pokreće odobrenje
verify(mockHandler, never()).requestApproval(any());
}
Svih 152 testova prošlo, HITL-srodni testovi sa nultom greškom.
11, Kompletno probajmo jednom
Nakon pokretanja PaiCLI, prvo uključite HITL:
> /hitl on
✅ HITL je omogućen, opasne operacije će tražiti potvrdu pre izvršenjaZatim neka Agent napiše jedan fajl:
> Pomozite mi da napišem test.txt u /tmp, sadržaj je "hello world"Agent razmisli, okidaće write_file alat, na ekranu će se pojaviti:

Unesite y da odobrite, Agent nastavlja izvršavanje. Unesite n da odbijete, Agent dobija feedback odbijanja i ponovo planira.

Ovo je kompletno iskustvo HITL.

Ako ne želite svaki put ručno potvrđivati, za grupu poznat sigurnih write_file operacija možete uneti a, kasnije write_file pozivi će automatski proći, do /clear.
ending
Šest perioda, PaiCLI se od malog ReAct Agent-a koji samo "misli-čini", postepeno evoluirao u Agent sistem koji ima plan, memoriju, pretragu, saradnju, kontrolu rizika.
Mnogi ljudi prva reakcija je "neka LLM sudara da li je opasno, nije li pametnije". Ali pametno nije isto što i pouzdano. Izvršenje alata je determinističko, sudra "da li treba presresti" isto treba biti determinističko.
Jednostavne stvari su često pouzdanije od pametnih. Jedan red Set.contains() je pouzdaniji od jednog LLM poziva.

ULOŽAVANJE RESUME-A
Naziv projekta: PaiCLI — Java Agent CLI
Opis projekta: Komandna linija Java Agent-a izgrađena od nule na osnovu ReAct paradigme, integrisana Plan-and-Execute, Memory, RAG, Multi-Agent i HITL ljudsko odobrenje, kompletno pokriva glavnu tehnologiju AI Agent-a.
Tehnološki stack: Java 17, Maven, GLM-5.1 veliki model, Jackson, JLine, SQLite (vektorsko skladištenje), JUnit 5, Mockito
Glavne odgovornosti:
- Implementacija HITL politike presretanja opasnih operacija na osnovu statičkih pravila (
ApprovalPolicy), obeležavanjewrite_file,execute_command,create_projectkao alate koji zahtevaju ljudsku potvrdu, izbegavajući nesupervizirano prepisivanje fajlova ili izvršavanje shell naredbi od strane Agent-a - Dizajn i implementacija nasleđenog sloja presretanja, kroz prekidanje
executeTool()injektira logiku odobrenja, HITL isključen nulti trošak, deljen u ReAct, Plan-and-Execute i Multi-Agent tri izvršne putanje - Korišćenje Record definiciju tipa podataka, implementacija algoritma širine prikaza (CJK/puni ugao/emoji po 2 kolone) osigurava renderovanje srpskog sadržaja u ASCII okviru odobrenja
- Kroz
synchronized+ConcurrentHashMaposigurava serijsko prikazivanje zahteva za odobrenje u scenariju više Agent-a konkurentno, podržava "odobri sve" keš na nivou sesije smanjuje frekvenciju ponovljenih potvrda - Korišćenje Mockito verifikuje logiku presretanja, pokrivenost politike i fail-safe ponašanje, uz ukupan broj test slučajeva projekta povećan na 152
