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_idpicks the template, for exampletwins,hypothesisorrules_recon. The id is reduced to a base name before any filesystem use.max_iterationsbounds the loop. The default is 5.LoopStatusreports progress:WORKING,EXECUTING,OBSERVING,DONE,FAILEDorMAX_REACHED.- Each turn is TOON:
current_thought,has_action,action,action_input,is_final_answer, and on the final turn a nestedfinal_result_json. - On
DONE,judgementscarries 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:
| Tool | Tier | What it does |
|---|---|---|
read_file | read | Reads a file from a confined path. Output is truncated. |
run_sql | write | Runs 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_claims | invoke | Hands 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,VisionServiceand 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,TriageReturnItemand 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=falseinFetchAnalysisResult.action_judgementsorExecuteLoopResponse.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.v1shape (JudgementonDecisionandActionCard). Any verdict key the model writes into its own output is stripped. - Configuration. Judging is on by default.
FACE_RALPH_JUDGEMENT=offdisables it.FACE_RALPH_JUDGE_MAX_ACTIONS(default 3) andFACE_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:
| Decision | RPC | What’s recorded |
|---|---|---|
| Approve or reject an action card | TwinsService.ExecuteAction | decision_trail. A reason is required on rejection. Approval seals the evidence document. |
| Address a remediation order | ComplianceService.AssignRemediation | assigned_by and assigned_at_unix |
| Standing routing rule | SetRemediationRoute | set_by and set_at_unix |
| Decide a remediation order | DecideRemediation | decided_by and decided_at_unix |
| Set a rule’s enforcement posture | RulesService.UpdateRulePolicy | The operator-owned half only. It can’t change findings. |
| Read actual customer rows | ConfigService.SampleConnectionDataset | A 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_sqlmay 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.