Skip to content
Case Agents

P2 · Trust Engineering · Productive Architecture & Deterministic Routing

The wrong tool is not a acceptable approach

285 tools, 30 queries, a critical mission banking domain. As a pre-execution barrier of four layers led 7 incorrect executions to zero — and how the solution evolved from a proof of concept (PoC) to a High performance productive architecture blueprint with Cold/Hot Path decoupling and SLAs sub-2ms.

Measured in the MVP benchmark · 30 queries · 53 training examples · molded tools · offline

100% Router Accuracy · benchmark
100% Hit Rate@2 · benchmark
0 Executions Incorrect · 20 queries
77,8% Economy of LLM · benchmark
72 Tests · 0 skips

Deterministic output of python -m candidate_starter.run_case Measured in the MVP benchmark — is not the system's accuracy in production or total financial economy. The ability is to choose the right tool; Trust is knowing when not to execute any.

In a sentence
A bank agent router that chooses 2 tools between 285 and blocks execution when the decision is unreliable enough. The ability is to choose the right tool; trust is to know when not to execute any.
Format
Technical Case
Depth
In-depth reading · ~3 min for central idea · ~31 min complete

You prefer the essentials first? Read synthesis (~1 min)

Video summary

The case in a video

"The Machine of Trust: Case Study in Banking Genetic Development" — general view before technical deepening: the problem, the Silent Crash, the barrier of Four guards and what the numbers prove -- and what they don't prove.

01 · The Enunciate

The project: what was asked in a banking agent brain

Build routing brain from a digital bank service agent. Before any expensive LLM call, the system decides the cheapest and safest way to answer each message.

The three pillars of architecture

Pillar Engineering Problem Code Component
1 · Router Sort Query in FAST_PATH (Cheers/FAQ) or AGENT (needs tool + reasoning). candidate_starter/router.py
2 · Retrieval Tool Select 2 more relevant tools between 285 before mounting the LLM prompt. candidate_starter/retrieval.py
3 · Evaluation Harness Measure quality, cost, latency and security in a deterministic way offline. candidate_starter/harness.py

The non-negotiable restriction of the domain

"This is a banking domain. An incorrect tool is not an acceptable approach. Never execute a tool if the decision is below safety thresholds. Never remove quality gates just to increase recall or economy. Do not use expected_tool the dataset within the production logic. This field can only be used by the harness to evaluate offline."

The Border: production does not see the template

What separates adjustment engineering from template is one line: expected_tool lives in evaluation layer and never in production logic.

Production logic router · retrieval · taxonomy

Decide with signs available at the time of application: confidence of the router, score of the retriever, relative margin and the verb of the query. Nothing more.

Assessment layer harness.py

expected_tool Do eval_dataset.json only read Here.offline to score Hit Rate after the decision has already been made.

Border Proof static lexical verification
grep -n "expected_tool" candidate_starter/retrieval.py candidate_starter/router.py candidate_starter/taxonomy.py
# Saída: zero resultados.

02 · Lifecycle & Engineering Decision

Two Strategies, One Domain: Prototyping Agility vs Robustez Enterprise

In senior software engineering, a technical solution does not exist in the vacuum. The official repository case-agents implements and documents two active architectural approaches in parallel branches, demonstrating the deliberate transition of an exploratory Concept Proof (PoC) to a mission-critical governance system.

Approach 1: Lean PoC

Branch: feature/solucao-enxuta
Agility & MVP

Strict focus on minimum testing speed and footprint. Implement router and the retriever in direct Python: Platt calibration, capacity collapse and steering guard exist, but embedded in router.py and retrieval.py, no taxonomy module and no ADRs. Ideal for quick validation of product viability and starting benchmark.

Test Coverage 15 unit tests
Catalog Governance Built-in dictionary (12 capacities)
Router Calibration Platt Scaling (cv=3)
Directional Guard Partial (reading→written by prefix)

Pros & Trade-offs:

  • Pro: Clean code (~560 lines in four modules and a script), easy to audit and deliver in a few days.
  • Pro: Low cognitive friction for early-stage teams.
  • Against: Branch Harness Prints 26.3% of Precision@2, because it compares the name of the variant with the canonical name; recounted by capacity, are 20/20 in the top-2 (see Statistics in Code).
  • Against: 16.7% abstentions (5 out of 30), and the steering guard only covers reading→written in the tools that the prefix of the name reveals (167 out of 285 tools).

Approach 2: SDD Enterprise & Trust

Branch: main (old solucao-sdd-vibe)
Production & Scale

Focus on banking reliability, compliance and industrial life cycle. Adopt Spec-Driven Development (SDD), 9 formal ADRs, canonical capacity taxonomy, Platt Scaling calibration, 4-layer barrier (including Steering Guard) and the blueprint for high performance compiled inference.

Test Coverage 72 tests / 0 skips
Catalog Governance 14 capabilities + collapse
Router Calibration Platt Scaling Sigmoid
Directional Guard G4 Active (Read != Write)

Pros & Trade-offs:

  • Pro: 100% Hit Rate@2 and zero incorrect executions formally guaranteed.
  • Pro: Proven protection against damage to the client's bank record (G4).
  • Pro: Ready for industrial scale: manifesto APM.yml and ADR-009 (Rust/Go).
  • Against: Requires continuous discipline of catalog governance and parity tests.

The lesson of duality: In the validation phase (PoC), the Shiny Solution is tempting by speed. However, in finance or health, the cost of incorrect execution in production exceeds the cost of governance infinitely. A branch main there is to demonstrate how to overcome this barrier with engineering rigor.

03 · The Measure Failure

Silent Crash: the failure that 54 tests did not catch

In an intermediate iteration (commit c8f30d2), the project showed: 54 tests on green, 100% router, 100% Hit Rate@2, zero incorrect executions and status approved with praise. An external review, however, requested a collision test between reading and writing vocabulary.

Customer Query: "What's the email registered to my account?" — a READING.

Before — 3 guards Execute writing
Top-1 · atualizar_email — WRITER 0.4795
Top-2 · confirmar_email_cadastrado 0.2554

Margin (0.4795 − 0.2554) / 0.4795 = 0.467 >> 0.25 → the margin guard it won't catch. High trust, high score, loose margin: IMPLEMENTATION — The officer alters the record of those who only asked.

Then — steering guard (G4) Run read
Top-1 · consultar_email_vinculado_conta — READING 0.3891
atualizar_email — WRITTEN, DISAPPEARED —

The Query is for reading; the writing ability is discarded before the ranking. Margin (0.3891 − 0.1792) / 0.3891 = 0.539 > 0.25 → runs the consultation, dry cut, no transition.

None of the three old guards took it: the mistake It's not a tie. — is a decision confident and wrong. This illustrates the central thesis of the pillar Trust Engineering: the most dangerous error in AI does not generate code exception. He passes the green and makes a ruinous decision with loose margins.

04 · Structural Diagnosis

Why no vectorization weight solves the problem

When the problem appeared, the first attempt was to adjust the combination of weights between lexical search and intent search (score = (1−α)·lexical + α·intent). The mathematical demonstration below proves why this is impossible:

Arithmetic Analysis Writing Query vs Reading Tool
Query: "Quero mudar o e-mail vinculado a minha conta" (ESCRITA)

consultar_email_vinculado_conta  -> lexical = 0.8457 | intent = 0.4400
atualizar_email                  -> lexical = 0.1983 | intent = 0.3200

Para a escrita vencer por ajuste de α:
(1 - α)·0.1983 + α·0.3200 > (1 - α)·0.8457 + α·0.4400
α·(0.3200 - 0.4400 - 0.1983 + 0.8457) > 0.8457 - 0.1983
0.5274·α > 0.6474  =>  α > 1.227 (IMPOSSÍVEL, pois α ∈ [0, 1])

The structural lesson: Reading and writing on the same object share all nouns (e-mail, conta, cadastro). The difference between consultation and change is one word: the verb. In a bag of words with TF-IDF, this verb is diluted between ten tokens. The direction of the operation is not recoverable by textual similarity — it needs to be a semantic restriction declared.

05 · The Rigid Solution

The 4 Layers Pre-Execution Barrier

Four deterministic guards chained up. The Query only advances to the LLM and to the execution of tools if it passes through all four locks. If any guard fails, the system diverts to abstinence or human care.

G1

Route Trust

Calibrated Router < 0.75 trust? If you fail, the retriever is not invoked.

HUMAN_FALLBACK_LOW_CONFIDENCE
G2

Minimum Candidate Score

No retriever candidate reaches the minimum lexical/intent score of 0.10?

ABSTAIN_LOW_SCORE
G3

Relative Margin between Candidates

Margin (s₁ − s₂)/s₁ < 0.25? ? Top-1 and top-2 are too close (technical tie).

AMBIGUOUS_CONFIRMATION
G4

Steering Guard (Read vs Writing)

Direction extracted from the Query verb is different from the direction (mode: read|write) declared in the tool?

Discard (Read→Writing) or Debasement

The deliberate asymmetry of Guard 4

  • WRITTEN READ CHERY + WRITTEN Tool: Total disposal of tool. Changing the record of those who just asked is irreversible damage to the customer.
  • Written Query + READING Tool: Score down, don't rule it out. Refer to those who requested change is unfulfilled task — recoverable in the following message.

06 · MVP Pipeline

Architecture in 4 Layers (Mathematics Validation in Python)

the complete flow of the application until the execution authorisation on the validation engine:

  1. Query user
  2. Router — TF-IDF + LogisticRegression + Platt scaling FAST_PATH → local response, zero cost of LLM AGENT below
  3. Taxonomy — catalogue governance: capacity
  4. Retriever — top-k different capacities
  5. Barrier — 4 guards in order: G1 · G2 · G3 · G4 Failure in any guard → Abstention or human care
  6. LLM — called only after the barrier authorized
  7. Tool execution
Router, taxonomy, retriever and guards are deterministic; the LLM enters only after authorization.
1 · Router calibration router.py

TfidfVectorizer(ngram_range=(1,2)) + LogisticRegression calibrated via CalibratedClassifierCV(method="sigmoid") Platt scaling. Solves the problem of underconfidence by regularization L2 in small dataset without artificially inflating probabilities.

2 · Taxonomy & Governance taxonomy.py

Declaration of 14 canonical capabilities and 45 variants with operation mode (read or write). Map the user vocabulary (aliases) for intentions and solve semantic duplicates of the catalog.

3 · Multi-field Retriever retrieval.py

BM25F score combination: score = 0.5·lexical + 0.5·intent. The alias glossary is indexed in a separate field to prevent L2 normalization from diluting the original terms. It performs capacity collapse by returning different canonical top-k.

4 · Barrier & Harness harness.py

Quality Gate tri-state (APROVADO, REPROVADO, INDETERMINADO). Validate the four guards before the execution mock. If there is no transactional query in the benchmark, the result is marked as UNDETERMINED by security.

07 · Statistics in the Code

The five statistical techniques of the case and Python implementing them

Whoever decides on this pipeline is not a language model. There are five classic statistical and statistical techniques information retrieval, each responding to a concrete problem of banking service. Each technique below has two layers: the concept, in simple language, and the Python excerpt that implements it.

The snippets are from the branch feature/solucao-enxuta (commit) e2dcd7f), the simplified version: four modules and an execution script, about 560 lines, without taxonomy.py And no ADRs. Where the main does different, a note warns. The numbers come from the output of python -m candidate_starter.run_case in this branch and offline recounts on the same report. It's not the numbers of main quoted in the rest from the page.

Technique Operational problem Where in code
1 · TF-IDF + logistic regression Decide, cheap and in milliseconds, if the message needs an agent. router.py · QueryRouter
2 · Platt scaling + cross-validation stratified Doing the number "0.75" means a probability of right. router.py · fit()
3 · Multi-field cosine + capacity collapse Find two tools between 285 without returning two duplicates. retrieval.py · ToolRetriever.search
4 · Thresholds, relative margin and steering guard Know when no execute. harness.py · run_harness; retrieval.py
5 · Evaluation and economics metrics Prove it works without counting how I get what's just abstention. harness.py · compute_*

1 · Supervised classification: TF-IDF and logistic regression

Operational problem. Greetings and FAQ questions ("What time is it?") do not require LLM or tool. Answer this locally has zero cost of LLM. The router needs to separate FAST_PATH of AGENT in milliseconds, before any man calls.

Concept. Supervised rating is to teach with examples already labeled: here, 53 messages marked FAST_PATH (25) or AGENT (28) The model learns what separates the two classes and applies this to new messages, in two steps: turn text into numbers and learn a boundary between classes.

O TF-IDF gives each term a weight that grows with its frequency in the message (TF) and decreases with the number of messages from the set containing it (IDF). "De" appears in almost everything and weighs little; "block" appears in few and weighs much. With n-grams from 1 to 2 terms, pairs like "second way" also saw terms. Each message becomes a sparse vector (almost everything zero) length 1.

A logistic regression transforms a weighted sum of the terms into a probability, between 0 and 1, of a message be AGENT. A regularisation L2 penalty large weights, for model not decorate 53 examples; C control force (1.0 is the scikit-learn pattern and is the value used). It is a linear and light classifier, and each term has a coefficient that you can read, which matters in an audit.

Formulas what scikit-learn calculates below
tf-idf(t, d) = tf(t, d) × idf(t)          idf(t) = ln((1 + n) / (1 + df(t))) + 1
vetor(d)     = tf-idf(d) / ‖tf-idf(d)‖₂             (norma L2 = 1)
P(AGENT | x) = σ(w·x + b) = 1 / (1 + e^−(w·x + b))
o treino minimiza:  ½‖w‖² + C · Σ log-loss(yᵢ, P(xᵢ))       (C = 1,0)
router.py lines 18–23
self._vectorizer = TfidfVectorizer(
    ngram_range=(1, 2),
    min_df=1,
    preprocessor=normalize,
)
self._base_clf = LogisticRegression(C=1.0, max_iter=1000, random_state=42)
normalization.py lines 22–42
def normalize(text: str) -> str:
    if not text:
        return ""

    # 1. Lowercase
    text = text.lower()

    # 2. Remover acentos
    text = remove_accents(text)

    # 3. Variantes ortográficas do domínio (antes de remover pontuação se houver hífens)
    for pattern, replacement in DOMAIN_VARIANTS:
        text = re.sub(pattern, replacement, text)

    # 4. Remover caracteres especiais (manter apenas letras, números e espaços)
    text = re.sub(r"[^a-z0-9\s]", " ", text)

    # 5. Colapsar múltiplos espaços
    text = re.sub(r"\s+", " ", text).strip()

    return text

normalize() is the preprocessor vectorizer, then the same treatment (low, without accent, domain variants as "second way" → segundavia, no score) in practice and consultation. If the two were normalized differently, the vocabulary learned would not marry the Real question.

Measured in this branch:

  • Vocabulary of 378 terms (unigrams and bigrams) on the 53 training examples.
  • Router 30/30 in benchmark; confusion matrix FAST_PATH 10 · 0 / AGENT 0 · 20.
  • predict(), already with the calibration of technique 2: median of 2,2 ms and p95 of 4,4 ms in 200 local calls (Windows, scikit-learn 1.9).

2 · Probability calibration: Platt scaling and cross-validation stratified

Operational problem. Guard G1 threshold of 0.75 (section The Barrier) is only a policy risk if "0.75" means "the model fits in about 75% of cases". Regular logistic regression, with 53 examples and 378 terms, shrink the weights and return shy odds, close to 0.5, even when the class is right. No calibration, in this branch, 20 of the 20 messages AGENT Benchmark is below 0.75 (the highest confidence is 0.713), Although the router hits them all. The threshold would no longer separate a good decision from a bad decision and only rule out coverage.

Concept. Calibrate is to correct the number scale without changing what the model decides. O Platt schedule trains a second logistic regression of a single variable on the gross score f The model. Parameters A and B are estimated by maximum likelihood in forecasts made on examples that the template no saw in practice (out-of-fold: predictions about the part of the data left out in each round). To estimate them in the very examples of training would measure the model memory, not the ability to generalize.

A cross-validation stratified divides the 53 examples into 3 folds that preserve the proportion of classes; each fold serves once as an unseen set. No shuffle (shuffle=False), the folds are the same in Any machine, and the tests are reproducible. Code does not call StratifiedKFold directly: pass cv=3 to a classifier makes the scikit-learn use this divider (checked in version 1.9). The number of folds come from the smallest class: 3 if it has at least 3 examples, 2 if it has 2, and without calibration below that, for the fit() do not break into tiny test sets.

Why sigmoid and not isotonic. Isotonic calibration is nonparametric: with few data it overfits and saturates confidence around 1, the opposite problem. The sigmoid has two parameters and is stable. A main measured this comparison in the ADR-007.

Formulas Platt schedule and folds
Platt:  p(AGENT | f) = 1 / (1 + exp(A·f + B))      A, B por máxima verossimilhança, em dados fora da dobra
Dobras: StratifiedKFold(n_splits=3, shuffle=False)   → 3 modelos calibrados; a probabilidade final é a média
router.py lines 31–47
# Contar menor número de amostras por classe
unique_labels, counts = np.unique(labels, return_counts=True)
min_class_count = int(np.min(counts))

if min_class_count >= 3:
    cv = 3
elif min_class_count >= 2:
    cv = 2
else:
    cv = None

if cv is not None:
    self._clf = CalibratedClassifierCV(estimator=self._base_clf, method="sigmoid", cv=cv)
else:
    self._clf = self._base_clf

self._clf.fit(X, labels)
router.py lines 59–63
X_q = self._vectorizer.transform([query])
probs = self._clf.predict_proba(X_q)[0]
best_idx = np.argmax(probs)
route = str(self.classes_[best_idx])
confidence = float(probs[best_idx])

Measured in this branch:

  • No calibration, 20 of 20 messages AGENT are below 0.75. With calibration, 1 of 20 (0,732).
  • In the entire benchmark, G1 barred 2 messages: "I changed number, I need to update the registered phone" (AGENT, 0.732) and "What documents do I need to open an account?" (FAST_PATH, 0.718). In this branch, G1 is for both routes.
  • The least confidence accepted is 0,753: 0.003 slack on the threshold. Calibration corrected the scale, but the margin remains narrow.
  • The repository does not measure the calibration itself (Brier score, expected calibration error, reliability curve), only its effect on the threshold. With 20 queries AGENT, a reliability curve would have few points per range.

3 · Vector recovery: Cosine similarity, multi-field score and capacity collapse

Operational problem. The catalog has 285 tools. Putting all in the prompt of an LLM bursts the context, confuses the model and cost expensive, so the retriever chooses the 2 most relevant before any call. And the catalogue has duplicates: 8 different tools make "get the invoice".

Concept. A Cosine similarity measures the angle between two vectors: 1 when they have the same weighted vocabulary, 0 when they do not share any term. As TF-IDF already delivers vectors of length 1, cosine is only the scalar product, that is, a multiplication of sparse matrix between the query and the 285 tools.

The score combines two fields with equal weight. The tool document is nome + descrição + categoria; Intention field is nome + descrição. Keep fields separate, instead of concatenating everything, exists for that L2 normalization does not dilute the original terms when adding synonyms to the same text (the ADR-006 ed main document this problem). A list of functional words (a, de, para, quero...) comes out of vocabulary first, for a Query short not only score by prepositions.

A limit on this branch. The two fields come out of the same text: they differ only by category. Measured, the correlation between two similarities is at least 0.97, and the top-1 of the combined score is identical to that of the single field in the 30 queries. Here, α = 0,5 It hardly changes the result. A second field only helps when it brings new information, like the handwritten glossary of main.

O capacity collapse groups duplicates with an embedded dictionary (CANONICAL_TOOL_MAP: 49 tools in 12 capacities) and holds the highest score per group (max-pooling, a maximum aggregation). Like this. k=2 returns two distinct capacities, not two variants of it. Then the code orders by score decreasing and cuts into k. Candidates with score 0 are discarded: a query without any common word with the catalog returns empty list. Empates follow the order of the catalogue, because the sorted Python is stable. This is deterministic for the same file, but it is not an explicit tiebreaker (a main Untie by the name of capacity).

Formulas cosine, score and collapse
cos(q, d)          = (q · d) / (‖q‖ · ‖d‖) = q · d          (vetores TF-IDF com ‖v‖₂ = 1)
score(ferramenta)  = (1 − α) · cos(q, documento) + α · cos(q, intenção)        α = 0,5
score(capacidade)  = max { score(t) : t pertence à capacidade }                (colapso por máximo)
retrieval.py lines 164–177
doc_texts = []
intent_texts = []

for tool in tools:
    # Document text: name + description + category
    doc_text = f"{tool.name} {tool.description} {tool.category}"
    doc_texts.append(doc_text)

    # Intent text: name.replace("_", " ") + description
    intent_text = f"{tool.name.replace('_', ' ')} {tool.description}"
    intent_texts.append(intent_text)

self._doc_matrix = self._doc_vectorizer.fit_transform(doc_texts)
self._intent_matrix = self._intent_vectorizer.fit_transform(intent_texts)
retrieval.py lines 195–220
doc_sims = cosine_similarity(q_doc_vec, self._doc_matrix)[0]
intent_sims = cosine_similarity(q_intent_vec, self._intent_matrix)[0]

combined_scores = (1 - self.alpha) * doc_sims + self.alpha * intent_sims

# Apply direction guard & canonical collapsing
canonical_best: Dict[str, Tuple[str, float]] = {}

for idx, tool in enumerate(self._tools):
    score = float(combined_scores[idx])
    # … (guarda de direção: técnica 4)
    if score <= 0:
        continue

    canon_group = get_canonical_name(tool.name)
    if canon_group not in canonical_best or score > canonical_best[canon_group][1]:
        canonical_best[canon_group] = (tool.name, score)

# Sort matches by score descending
sorted_matches = sorted(canonical_best.values(), key=lambda item: item[1], reverse=True)
top_k_matches = [ToolMatch(name=name, score=score) for name, score in sorted_matches[:k]]

Measured in this branch: recounting by capacity (get_canonical_name on the result), expected capacity is in the top-1 in 19 of 20 transactional and top-2 in 20 of 20. The report Print shows another number. The explanation is in technique 5.

4 · Decision under uncertainty: thresholds, relative margin and steering guard

Operational problem. Choosing the best tool is not enough: the system needs to know when no execute. A mistake that passes in silence (see The Silent Crash) costs more than a confirmation question.

Concept. Instead of always acting on the best candidate, the pipeline tests three statistical signals, in order, and only Execute if everyone passes. None of them read the expected_tool the gauge.

  • G1 · confidence: the calibrated probability of the router is ≥ 0.75.
  • G2 · minimum relevance: the top-1 score is ≥ 0.10, a floor against weddings by chance.
  • G3 · relative margin: (s₁ − s₂) / s₁ ≥ 0,25. Measure how much the first candidate wins the second in proportion of the score itself. It is relative not to depend on the scale: a difference of 0.03 weighs different when the Top-1 is 0.18 and when it's 0.80.
harness.py lines 107–141
# Guard Rail 1: Router confidence >= 0.75
confidence = route_result.confidence if route_result.confidence is not None else 1.0
if confidence < 0.75:
    row["final_status"] = "HUMAN_FALLBACK_LOW_CONFIDENCE"
    abstentions += 1
elif route_result.route == "FAST_PATH":
    fast_path_answer(query)
else:
    retrieval_result = retriever.search(query, k=k)
    # …
    # Guard Rail 2: Top-1 score >= 0.10
    top1_score = top_k_matches[0].score if top_k_matches else 0.0
    if top1_score < 0.10:
        row["final_status"] = "ABSTAIN_LOW_SCORE"
        abstentions += 1
    else:
        # Guard Rail 3: Margin relative >= 0.25
        top2_score = top_k_matches[1].score if len(top_k_matches) > 1 else 0.0
        margin = (top1_score - top2_score) / top1_score if top1_score > 0 else 0.0
        if margin < 0.25:
            row["final_status"] = "AMBIGUOUS_CONFIRMATION"
            abstentions += 1
        else:
            # Executa a tool top-1
            mock_tool_execution(top_k_names[0], query)

The margin in three real issues, all diverted by G3 in the benchmark:

Query Top-1 and top-2 (score) Margin Result
"I lost my card on the street, I need to block it now" bloquear_cartao 0,180 · bloquear_cartao_temporariamente 0,177 0,017 AMBIGUOUS_CONFIRMATION
"I changed addresses, how do I update my delivery?" alterar_endereco 0,474 · consultar_endereco_cadastrado 0,434 0,084 AMBIGUOUS_CONFIRMATION
"Did my new card arrive, how do I unlock?" instrucoes_desbloqueio_cartao_novo 0,546 · confirmar_recebimento_cartao_novo 0,420 0,231 AMBIGUOUS_CONFIRMATION

The third failure by 0.019: 0.25 is a policy, not a constant of nature. In this branch the three thresholds are literal in code; main are named constants, and the margin is harness parameter.

G4 · steering guard. It is not a measure of similarity, and the reason is statistical: reading and writing about the even given divide almost all the terms (the section Why no weight solves shows the arithmetic). Then the direction enters as a declared sign: Query verbs and keywords say whether it is reading or writing, and the tool name prefix (consultar_, atualizar_...) says what kind she is. Reading Query with writing tool: zeroed score.

retrieval.py lines 119–137 ↗ and 207–209 ↗
def infer_query_direction(query_norm: str) -> str:
    """Inferir direção da query: 'read', 'write', ou 'unknown'."""
    has_read = any(re.search(rf"\b{kw}\b", query_norm) for kw in READ_QUERY_KEYWORDS)
    has_write = any(re.search(rf"\b{kw}\b", query_norm) for kw in WRITE_QUERY_KEYWORDS)

    if has_read and not has_write:
        return "read"
    if has_write and not has_read:
        return "write"
    return "unknown"


def infer_tool_direction(tool_name: str) -> str:
    """Inferir direção da ferramenta pelo prefixo."""
    if tool_name.startswith(READ_TOOL_PREFIXES):
        return "read"
    if tool_name.startswith(WRITE_TOOL_PREFIXES):
        return "write"
    return "unknown"

# …
# Direction guard rule: read query + write tool -> score = 0
if query_direction == "read" and tool_direction == "write":
    score = 0.0

Measured and limited in this branch:

  • Protected reading. "What's the email registered to my account?" confirmar_email_cadastrado (reading) first. atualizar_email doesn't appear among the first three.
  • Writing→reading is not treated. The lean specification expected to lower the reading tool, but the code does nothing in this Meaning. "I want to change the email linked to my account" executes consultar_email_vinculado_conta (score 0.664): It doesn't change anything, but it doesn't comply.
  • You only see what the name reveals. By prefix, 82 of the 285 tools are read, 85 are write and 118 tools (41%) they do not reveal direction by name and pass without restriction.
  • Reading grouped under writing. The collapse dictionary puts consultar_email_vinculado_conta and confirmar_email_cadastrado, reading tools, under atualizar_emailThat's writing. A main prohibits this by invariant: reading and writing do not group.

5 · Evaluation and economics metrics

Operational problem. Without measurement, "works" is opinion. And a misread metric is worse than none: cost savings It goes up when the system abstains further, even if it answers less.

Concept. A accuracy is the proportion of correct answers: correct answers divided by the total. A confusion matrix 2×2 separates the two errors possible, which cost different things: send an FAQ to the agent spends LLM for nothing; send a transactional request to FAST_PATH gives the customer a generic response in place of the operation. The code initializes all cells with zero, then the structure is complete even without cross errors.

O hit rate@k is the fraction of the queries in which the expected tool appears among the first k. Not Precision@K classic (relevant between k, divided by k): with one relevant tool and k = 2, maximum precision@2 would be 0.5. The function if flame compute_precision_at_kBut what she calculates is hit rate.

A economy is the percentage reduction against baseline (send all message to the LLM, with all the tools in the prompt), with guard to baseline zero. In MVP, costs and latencies come from mock constants (US$ 0.03 per baseline call, US$ 0.01 per called the agent), so the percentage measures the pipeline structure, not a real price.

Formulas Harness metrics
acurácia     = acertos / total
hit rate@k   = (queries com a ferramenta esperada no top-k) / (queries avaliadas)
economia %   = (baseline − pipeline) / baseline × 100            (0 se baseline = 0)
harness.py lines 22–28 ↗, 36–40 ↗ and 52–54 ↗
correct = sum(1 for yt, yp in zip(y_true, y_pred) if yt == yp)
accuracy = correct / len(y_true)

matrix = {l1: {l2: 0 for l2 in labels} for l1 in labels}
for yt, yp in zip(y_true, y_pred):
    if yt in matrix and yp in matrix[yt]:
        matrix[yt][yp] += 1
# …
def compute_precision_at_k(hits: List[int]) -> float:
    """Calcula Precision@K a partir de uma lista de 0/1 (acertou ou não a tool)."""
    if not hits:
        return 0.0
    return float(sum(hits) / len(hits))
# …
cost_savings = 0.0
if baseline_cost_usd > 0:
    cost_savings = ((baseline_cost_usd - smart_cost_usd) / baseline_cost_usd) * 100.0

Output of python -m candidate_starter.run_case in lean branch, 30 queries:

Metric Value Origin
Router accuracy 100% (30/30) · matrix 10 · 0 / 0 · 20 printed
precision_at_k (k = 2) 26,3% (5/19) printed
Expected capacity per capacity top-1 19/20 · top-2 20/20 offline recount
Abstentions 5 (16.7%): 2 per G1.0 per G2, 3 per G3 printed; division by guard, recount
Executions 16, of which 15 in expected capacity offline recount
Cost: pipeline/baseline US$ 0.16003 / US$ 0.90000 → savings of 82.2% printed

Because 26.3% is not the quality of recovery. O ToolMatch.name which the retriever returns is the name of variant that married (e.g. verificar_saldo_conta_corrente), and expected_tool the dataset is the name canonical (consultar_saldo). The string comparison fails even with the right ability. Recounting for capacity, the result is 19/20 and 20/20. The denominator is also 19, not 20: the Query barred by G1 never reached the retriever and It's out of line.

Because 82.2% is larger than 77.8% of the rest of the page. Five messages were diverted by abstention and did not call LLM. It's the the same effect as described in Economy without success rate is disguised risk: the economy rises when the system does Less. The execution that comes out of the expected capacity is "Send the pdf of my current invoice" → reenviar_fatura_email, that the dictionary does not group into consultar_fatura. And the right 15 include the email query, whose reading tool dictionary groups under atualizar_email. The lean branch does not calculate incorrect executions or the balance point of the human service cost: these two accounts only exist in the main.

lean branch × main, technique by technique

Point lean Branch main
Calibration folds cv = 3 (less class ≥ 3) until 5: min(5, menor classe)
Second score field derived from name and description: almost redundant Handwritten glossary by capacity, indexed in separate field
Capacity collapse built-in dictionary: 49 variants, 12 capacities taxonomy.py: 14 capacities, 45 variants, with tested invariants
Untie stable order of the catalog direction, then −score, then capacity name
Score floor (G2) in harness; the retriever only discards score ≤ 0 min_score in the retriever, with 0,10 passed by those who mount it
Direction (G4) name prefix; read-only→write zera mode declared (59 of 285); reading→discard writing, writing→low reading
Metrics precision_at_k by exact name hit rate@1 and @k, correct and incorrect executions, balance point, quality gate tri-state

The five techniques are simple and cheap in both branches. What separates the lean from main is not a technique anymore sophisticated, but what surrounds each: a field with new information, declared direction, a metric contract that compares capacity with ability and an explicit tiebreaker.

08 · From proof of concept to production

High Performance Multi-Language Architecture (ADR-009 & RFC)

MVP Python pipeline proved the mathematical solidity of the engine: 100% accuracy and zero incorrect executions. However, transfer Python pods directly to critical banking production path (attending tens of thousands requests per second with SLA p99 < 1-3 ms) runs into unavoidable limitations: o Global Interpreter Lock (GIL), the Garbage Collection (GC) unpredictable jitter and the cost of dynamic dismatch.

The formalized engineering response in ADR-009 and RFC Architecture is the strict decoupling between Cold Path and Hot Path:

Cold Path: Search & MLOps

Python 3.12 · Offline / CI

Keeps the Python ecosystem where it is unbeatable: feature engineering, training with scikit-learn, calibration via CalibratedClassifierCV (Platt Scaling), SHAP and generation of deterministic artifacts.

  • ONNX Export: Compiled linear inference graph (modelo_router.onnx).
  • CSR Sparsa Matrix: The 285 documents in the catalogue are pre-computed and serialized in doc_matrix.csr.
  • Deterministic Vocabulary: IDF-indexed TF-IDF Dictionary (vocab_tfidf.json).
  • Banking immutability: Object Storage versioned artifacts signed with hash SHA-256.
  • Hard Insulation: The production runtime never matters Python, numpy or scikit-learn.

Hot Path: OmniRoute Gateway

Rust / Go · Online Stateless

Single stateless input point via gRPC or HTTP/2. Built-in inference motor (in-process), eliminating network jumps, intermediate serialization or IPC calls.

  • Zero Garbage Collection: Rust operates without garbage collector, eliminating jitter and ensuring deterministic latency p99 (< 1-3 ms).
  • Zero-allocation on Hot Path: Static pools and pre-allocated buffers via tokio and memory arenas.
  • Guard of 4 Layers in RAM: Router, Retriever (multiplication of parallel sparse matrix) and Guard Rails executed in < 10 μs.
  • Distroless Container: Single static torque (~20 MB) with minimal attack surface and memory safety proven by the compiler.
  • Slot-Filling Rinse: The LLM is only invoked when the tool has already been isolated and authorized.

Technology Decision Matrix: Rust vs Go

The architecture document thoroughly compares engineering options for OmniRoute Gateway:

Assessment Criteria Rust (Primary Recommended) Go (secondary alternative)
Memory Management Zero GC (Ownership & Borrow Checker). No unpredictable breaks. Garbage Collector with sub-millissecond pauses, but with jitter risk under high string volume.
Latency p99 Sub-milissecond (< 1 ms) deterministic and predictable. 2 ms to 5 ms (small oscillations during GC collections).
Competition tokio (I/O non-blocking) + rayon (data parallelism). Native Goroutines and canals. Excellent I/O competition.
Linear algebra & ONNX Crate ort (official ONNX Runtime zero-overhead bindings) + sprs (CSR). onnxruntime-go via CGo (context exchange cost Go ↔ C).
Compliance and Security Formal prevention of data races and buffer overflows in compilation time. Memory safe, but at risk of excessive allocations in the heap.
Artifact of Delivery Static torque (~15-25 MB), container distroless. Static torque (~20-30 MB), container distroless.

The Architecture Decision Ecosystem (9 ADRs)

Each structural choice was recorded according to the Architecture Decision Record (ADR), ensuring that any new team member understands the context, discarded alternatives and consequences:

10 · Measured Evolution

Economy without success rate is disguised risk

As Goodhart's Law ("when a metric becomes a goal, it ceases to be a good metric"), blindly optimizing cost savings led the initial pipeline to abstain from almost half of calls, artificially inflating the percentage of savings.

Metric / Iteration Initial commit (fc830dc) Crash Iteration (c8f30d2) Published version (1f780c0)
Automated Tests 35 54 72 passed, 0 skipped
Hit Rate@2 35% 100% 100%
Correct Top-1 Execution 20% (4/20) 95% (19/20) 100% (20/20)
Incorrect Executions 7 incorrect 0 (but vulnerable) 0 incorrect
Reading Solving Writing Not measured Yes (margin 0.47) Impossible (G4)
Fallback for Low Trust 9 abstentions 0 0
Economy of LLM measured (benchmark) 87.8% (inflated) 78,9% 77.8% (real net)
Harness Status REPROVED APPROVED APPROVED IN BENCHMARK

Why did the economy fall from 87.8% to 77.8%? In the first version, 9 improper abstentions avoided LLM calls, inflating the economy while delivering only 20% success. The final version solves 20 out of 20 transactional issues with 100% hit and paid LLM for all of them.

What 77.8% measures — and what does not measure. It is the reduction of cost of LLM calls between the smart pipeline and the "everything for LLM" baseline on the offline benchmark. It's not a financial economy Total system: the cost of human fallback, for example, is premise and not measurement — so report public cost_savings_pct_on_resolved, deferred_to_human and human_fallback_breakeven_cost_usd side by side.

📄 Baixar candidate_report.json (Relatório Oficial de Benchmark)

11 · The Case

How was developed: the agentic method with embedded criticism

Development didn't follow a straight line. Two rounds were driven by external adverse review, where each note generated a diagnostic cycle, ADR measurement (Architecture Decision Record) and extended testing suite. The third had no reviewer: appeared because the steady state has been tested instead of green.

Round 1

Commit Review fc830dc

35% recall diagnosis, 7 incorrect executions and inflated economy. It produced ADRs 0006 and 0007, raising the suite from 35 to 54 tests.

Round 2

Adverse Review of the Committee c8f30d2

Mapping of 8 remaining risks. The recommended reading/writing collision test exposed the Silent Crash. It generated the ADR-0008 and added the collision tests to the suite.

Round 3

The saga of the origin of the report — commits 0d2c9ae … 1f780c0

Regenerate candidate_report.json And running the suite again revealed the count with a 1 skipped Too much. O skip was not benign conditional — it was a silent test giving up. Corrected, the suite closes in 72 passed, 0 skipped.

The origin that contaminated the measurement itself

The metric report is versioned — and a versioned JSON ages silently: the code changes, the file stays, and those who read believe in numbers that the current commit no longer produces. The project has a test that rewards decisions against the current code. This test had three defects of the same family, all with the report soiling the measurement that should audit it:

  1. Unsatisfactory assertion. Requirement snapshot.git_commit == HEAD. How to commit the report creates a new commit, snapshot always points to the previous one — the assertion could never pass. The real auditable property is another: between the commit which generated the report and the HEAD, nothing outside of reports/ changed — and the failure becomes name files Late.
  2. git_dirty self-sucking. build_snapshot() counted reports/ on the dirty tree check. As the report is written by the function itself, from the second execution onward it was declared irreproductive because of the file that it had just generated.
  3. _git().strip() ate the status prefix space. The filter sliced line[3:] Do --porcelain, but .strip() at the exit removes the initial space from the first line: the path exited displaced (eports/...), the filter never married and the test He would jump again in silence. Solved by delegating the filter to git itself (pathspec ':(exclude)reports'), without parsing.

A green suite with 1 skipped He looked healthy. It wasn't — the skip It was a test giving up silently, and it only appeared because the stationary state was tested (regenerate the report, rotate the suite again) instead of accepting the green. It is the third instance of the thesis of this page: Green metrics are not coverage.

Principles governing decisions

  • Measure before editing: No changes were made without registering the metric table before/after ADR.
  • Never move the beam: Easy solutions (such as climbing probability parameters to mask errors) were summarily rejected.
  • No Effect Tests: Tests that ensure that mock_tool_execution is never called If any guard fails.
  • Test steady state: It is not enough for the suite to pass once — it must continue passing after the system writes its own artifacts.

12 · Executable Specification

The Suite of 72 Tests as System Specification

Eight test files divided by security and integrity invariants. The part more valuable does not check a returned value; check that Nothing happened.:

test_taxonomy.py The most expensive security invariant in the domain, verified in the full ranking
def test_a_read_request_can_never_resolve_to_a_write_capability(retriever):
    """Antes da guarda de direcao, "Qual e o email cadastrado na minha conta?"
    devolvia atualizar_email (ESCRITA) em 1o lugar com margem relativa de 0.47 —
    acima do limiar de 0.25: a guarda de margem NAO pegava."""
    for query in read_requests:            # 5 consultas puras
        for m in retriever.search(query, k=3).matches:
            assert CAPABILITY_MODE.get(m.name) != "write"

Test Suite Categories

  • No effect: mock_tool_execution is never called when any guard fails (executed.assert_not_called()).
  • Taxonomy Integrity: 7 authorship invariants (existence in the catalogue, absence of collision, reading, writing, declared and inherited direction).
  • Generalization without Adjustment to Gabarite: 12 paraphrases written with totally absent vocabulary from the official dataset (9/12 on top-1, 11/12 on top-2).
  • Stability and Origin: Shuffle the order in the catalogue does not change any decision; the JSON report corresponds exactly to the commit and current code — and this check Can't jump (it was the fault of round 3).

13 · Technical Transparency

Real Project Limitations (What Benchmark Does Not Cover)

Publishing limitations is what separates advertising engineering. The 7 restrictions below are registered in the README of the official repository:

  1. Approved in this benchmark, not for live banking production: 30 queries, 53 training examples and molded tools. Real traffic with opponents requires layers of MFA, authorization and idempotence outside this scope.
  2. The steering guard only protects what is stated: 59 of the 285 tools have mode (14 canonical capacities + 45 variants). The others 226 They pass without driving restrictions. The right architectural place for mode is the register itself — is metadatad from the endpoint, not taxonomy.
  3. Paraphrased lexical recovery ceiling: The recall of pure paraphrases without keywords reaches 9/12 in the top-1. The next step requires dense/reranker embeddings.
  4. Manually maintained taxonomy: 14 capabilities and 45 variants configured in Python code over 285 tools.
  5. Guards 2 and 3 are no longer exercised by the official benchmark: With 20/20 and zero abstentions, they are proven only by tests of no effect. A benchmark that never triggers the security network doesn't show it works.
  6. Router calibration break is narrow: A Query AGENT less confident registers 0.7674 against the threshold of 0.75 — 0.017 Off duty, registered as a liability. (On the other hand the margins of the right decisions lie between 0.592 and 0.741 against the threshold of 0.25: this, yes, loose.)
  7. Human fallback cost is premise, not measurement: The reliable number is the equilibrium point, which is derived from measurements.

14 · Ecosystem & Links

Conceptual References of the Site

Trust Engineering

Foundation & Method

Repository & Architecture

In short

What is the Case Agents project

O case-agents is the implementation and benchmark of a routing brain for banking agent: decides if a query needs LLM, selects 2 tools between 285 registered and blocks execution when the decision is not reliable enough.

Case study Trust Engineering and AI Agent Engineering.

Frequently Asked Questions

Frequently Asked Questions

What is the Case Agents project?

It is the development and evaluation of the brain routing and selection of tools of a digital banking service agent. It classifies the message in FAST PATH or AGENT, retrieves the 2 most relevant tools between 285 and applies a 4-layer pre-execution barrier before calling any LLM.

What is Silent Crash measured in the project?

A failure in which a query query ("What is the email registered in my account?") resolved with high confidence and loose margin (0.47) for the registration change tool (update email). The failure went through 54 tests and 3 traditional guards without generating exception.

How does the steering guard work?

The Direction Guard (G4) analyzes the query verb and compares it to the mode of operation (read/write) stated in the tool. If the query is read and the tool is written, the system completely discards the tool to prevent irreversible damage to the client's registration.

How to achieve 100% success with cost savings?

Declaring the governance of the tool catalog (collapse of semantic duplicates in canonical capabilities) and applying the 4-layer pre-execution barrier (router confidence, minimum score, relative margin and direction). Net cost savings were 77.8%.

What is the strategy of two branches of the project?

The repository maintains two approaches in Git: a branch feature/solucao-swine, focused on rapid prototyping and validation of hypotheses with lean code, and the branch main (solucao-sdd-vibe), focused on robustness enterprise with Spec-Driven Development, 9 ADRs, canonical taxonomy, Platt Scaling calibration and blueprint of productive architecture.

How does High Performance Production Architecture (ADR-009) work?

The productive architecture rigidly separates Cold Path (training and calibration in Python 3.12 offline/CI) from Hot Path (OmniRoute Inference Gateway in Rust/Go stateless online). With built-in in-process engine, the hot path reaches latency p99 below 1-3 ms, eliminates Garbage Collection pauses and runs the guard rails directly in RAM memory.