Skip to content

Agents & oversight

FACE’s agents are prompt templates driven by one orchestrator, the Ralph loop. What an agent may do is bounded in four places:

  • the guardrails on its input,
  • an authorisation gate on its tools,
  • a judging ladder on the actions it proposes,
  • a human decision before anything is adopted.

The Ralph orchestrator

RalphOrchestratorService.ExecuteLoop runs a ReAct loop (thought, then action, then observation) and streams each step back as an ExecuteLoopResponse:

  • agent_id picks the template, for example twins, hypothesis or rules_recon. The id is reduced to a base name before any filesystem use.
  • max_iterations bounds the loop. The default is 5.
  • LoopStatus reports progress: WORKING, EXECUTING, OBSERVING, DONE, FAILED or MAX_REACHED.
  • Each turn is TOON: current_thought, has_action, action, action_input, is_final_answer, and on the final turn a nested final_result_json.
  • On DONE, judgements carries the judging ladder’s verdicts.

The same loop drives the Hypothesis Lab simulations and the interactive twin (StreamTwins).

Agent templates

The templates live in grpc/agents/templates/: twins, hypothesis, rules_recon, ai_posture, compliance, finance, claims, fulfilment, reverse_logistics, route_twin, predictive_maintenance, telemetry, documentation, vision, voice, cop and self_healing.

Templates are sent to the model verbatim. Nothing renders Jinja. Everything in a template file, including comments, is text the model reads. A template describes the role the model is asked to play. It is not a statement of the platform’s capabilities; for those, read the pages in this section.

Tools

The loop can dispatch three tools. Each passes a fail-closed authorisation gate (ralphToolGate) that checks the caller’s verified identity against the tool’s tier:

ToolTierWhat it does
read_filereadReads a file from a confined path. Output is truncated.
run_sqlwriteRuns a query through SqlService.ExecuteSelect, which refuses statements that fail its read-only shape check, against the connection the loop was started with.
delegate_to_claimsinvokeHands a sub-task to the claims agent.

No subject, no tool. A call with no authenticated subject is denied, not skipped. When a tool has no working configuration for the run, the loop tells the model up front and treats the first failure as final.

Guardrails (OpenBias)

Before a prompt reaches the model, an OpenBias judge evaluates it against rule sets under grpc/agents/openbias/rules/:

  • A global baseline is prepended to every judge.
  • Each service adds its own rules (TwinsService, ComplianceService, HypothesisService, VisionService and others).

The guardrails are wired and checked in three ways:

  • The agent lookup fails closed. Every agent id is mapped to its service’s guardrails. An agent with a template but no mapping is refused with FailedPrecondition, and the message describes a server configuration defect, not a user error.
  • Other entry points are guarded too. GenerateSOP, GenerateRoadmap, TriageReturnItem and the surveillance path each consult the matching judge.
  • The documents are checked against the code. A parity test compares every rule id and pattern in the rule documents with what the judges compile. Another test pins which judges are actually consulted.

How the rules are balanced. A rule that refuses ordinary work is treated as worse than one with a known gap. New patterns are checked against each service’s allowed examples, with must-not-block cases, before they land.

The judging ladder

Every logistics action a loop’s final answer proposes is judged before it’s accepted. That covers the twins orchestrator’s decisions and action_cards, other agents’ recommendations, and the fetch’s Phase 3 twins decision. The judge is the shared Runink judging ladder (inference/judgement), which CORE also uses.

  • What counts as evidence. The evidence is what the run gathered: its context and its successful tool observations. The claim judged is the action alone, never the orchestrator’s rationale for it.
  • Dissent triggers one revision. When the ladder dissents, the loop is sent back to revise once. An action still dissented from is removed. It survives only as accepted=false in FetchAnalysisResult.action_judgements or ExecuteLoopResponse.judgements.
  • Unable-to-judge is never concurrence. An action past the per-answer budget is kept as unable-to-judge, with the reason judgement-budget-exhausted.
  • Verdicts can’t be forged by the model. Verdicts travel out of band in the shared runink.ui.judgement.v1 shape (Judgement on Decision and ActionCard). Any verdict key the model writes into its own output is stripped.
  • Configuration. Judging is on by default. FACE_RALPH_JUDGEMENT=off disables it. FACE_RALPH_JUDGE_MAX_ACTIONS (default 3) and FACE_RALPH_JUDGE_REVISIONS (default 1) bound it.

The fast path

In front of the model-based judge sits a pinned tree model from ml/decide, running through judgefast. It’s controlled by FACE_FAST_JUDGEMENT, which is on by default.

  • It answers only at the ladder’s final gate, and the deterministic gates run first.
  • It never answers with no evidence.
  • It never answers on a claim that looks health-adjacent, meaning pharma, vaccine, patient and similar terms. That screen is lexical and biased towards abstaining.
  • It abstains on anything it can’t answer, and that goes to the model exactly as before.

Each model-based verdict records which model judged it in judged_by: either the fast model with its full sha256 digest, or llm.

Human-in-the-loop points

A person makes these decisions. The server records who made each one, from the verified session:

DecisionRPCWhat’s recorded
Approve or reject an action cardTwinsService.ExecuteActiondecision_trail. A reason is required on rejection. Approval seals the evidence document.
Address a remediation orderComplianceService.AssignRemediationassigned_by and assigned_at_unix
Standing routing ruleSetRemediationRouteset_by and set_at_unix
Decide a remediation orderDecideRemediationdecided_by and decided_at_unix
Set a rule’s enforcement postureRulesService.UpdateRulePolicyThe operator-owned half only. It can’t change findings.
Read actual customer rowsConfigService.SampleConnectionDatasetA separate, bounded call. Exploring a source never samples it. See Data sources.

Nothing FACE derives is sent, filed or settled without one of these decisions. Where an action does execute, for example an email draft through a connected account, its outcome is reported per artifact in not_executed. See Action cards & money.

What this does not establish

  • Guardrails make misuse harder; they don’t make it impossible. A prompt fence makes injection harder, and it does not solve it.
  • Judging is a second opinion. A concurring verdict means the judge found the action supported by the evidence the run gathered. It doesn’t show the action is right.
  • The tool gate authorises the tool, not the arguments. A caller permitted to use run_sql may still see the model choose a query they would not have written. Tenant scoping and the read-only shape check bound what that query can reach.
  • Recording a human decision is not enforcing it. The recorded decision shows who approved what. It doesn’t show that they reviewed it carefully.