Home › Enterprise Intelligence Architecture
Agentic Reasoning, Consensus & Deployment
Agents reasoning over a typed ontology rather than over text, five levels of tool orchestration, Raft/Paxos/CRDT for shared state, and shipping into disconnected or air-gapped fleets.
In this area
The fourth layer is where data finally makes decisions. An agent handed text chunks invents identifiers; an agent handed typed ontology objects does not. This module covers the three things that decide whether an agentic system survives production: the neuro-symbolic division of labour between a probabilistic model and a deterministic ontology engine, tool-surface engineering and its budgeted orchestration, and the mechanics of shared state — when you need Raft with a quorum, when a coordination-free CRDT is enough, and what exactly crosses the air gap when you ship into a disconnected enclave.
An agent over an ontology, not over text
A default RAG agent receives text chunks and guesses which entities are involved. An ontology-grounded agent receives typed objects with stable identity, typed links and a closed set of actions. The difference is not cosmetic — it changes three system properties at once.
- Grounding. Every claim resolves to an
object_id, not to a paragraph. The model cannot name a wagon that does not exist in the object store, because it never types the identifier — it returns a reference the store issued. - Authorization becomes decidable. ABAC policy is applied to the object and property before the model sees anything. Policy written into a prompt is a wish; policy in the ontology filter is a guarantee.
- Writes are validated. The model does not mutate state. It proposes a typed action; the symbolic layer checks preconditions and either commits or rejects with a reason code.
The neuro-symbolic division of labour. The probabilistic half (the LLM) turns intent into a candidate plan. The deterministic half (ontology plus rule engine) owns types, cardinalities, invariants and permissions. The rule with no exceptions: invariants never live in the probabilistic half. A model that honours 'never reroute a shipment under customs hold' 97% of the time violates it once every thirty-three calls.
What the tool surface actually looks like. search_objects(object_type, filter) returns object references, not markdown. get_links(id, link_type) walks the graph. propose_action(action_type, object_id, params, idempotency_key) opens a two-phase write. Vector search only narrows the candidate set here; the ontology predicate decides.
What changes in retrieval. The question stops being 'which chunk is most similar' and becomes 'which objects satisfy this predicate'. Vector search survives as a coarse candidate cut; link traversal does the rest — instead of stitching three paragraphs from three documents, the agent makes two get_links hops and answers a multi-hop question exactly. The side effect is pleasant and measurable: a typed answer over ten objects is a few hundred tokens rather than ten 400-token chunks.
Two-phase writes. Propose → validate → gate (policy or human) → commit under an idempotency key. The key is not optional: an agent that retried a step after a timeout must not reroute the shipment twice. Every commit leaves the actor, action type, object id and ontology version in the log — an audit trail a regulator can verify without ever reading a prompt.
# Ontology-grounded tool surface. The model never receives prose:
# every result is a typed object reference it can cite and act on.
TOOLS = [
{
'name': 'search_objects',
'description': 'Find objects of ONE ontology type. Returns object refs, not text.',
'input_schema': {
'type': 'object',
'properties': {
'object_type': {'enum': ['Shipment', 'Wagon', 'Customer']},
'filter': {'type': 'object'},
'limit': {'type': 'integer', 'maximum': 50}
},
'required': ['object_type', 'filter']
}
},
{
'name': 'propose_action',
'description': 'Propose a typed write. Symbolically validated before commit.',
'input_schema': {
'type': 'object',
'properties': {
'action_type': {'enum': ['reroute_shipment', 'flag_wagon']},
'object_id': {'type': 'string'},
'params': {'type': 'object'},
'idempotency_key': {'type': 'string'}
},
'required': ['action_type', 'object_id', 'idempotency_key']
}
}
]
def commit(proposal, actor, ontology, policy, log):
# The deterministic half owns types, invariants and permissions.
obj = ontology.get(proposal['object_id'])
if obj is None or obj.type not in ACTION_DOMAIN[proposal['action_type']]:
return Rejected('TYPE_MISMATCH', retryable=False)
if not policy.permits(actor, proposal['action_type'], obj):
return Rejected('FORBIDDEN', retryable=False) # never decided in a prompt
for inv in ontology.invariants(proposal['action_type']):
if not inv(obj, proposal['params']):
return Rejected('PRECONDITION_FAILED:' + inv.name, retryable=True)
# ordered, replicated, and idempotent by key (see E8.3)
return log.append(proposal, key=proposal['idempotency_key'])
In practice
A rail freight operator connected an agent to a 4.2M-object store (shipments, wagons, customs holds). Text-chunk RAG bound the correct entity in 61% of 'which shipments are at risk' queries; typed object tools raised that to 94%. Fabricated wagon numbers fell from 12% of answers to zero, because no identifier was model-generated any more.The anti-pattern
Serialising the whole subgraph into the prompt as JSON text and letting the model perform the joins. It inflates context, brings hallucinated identifiers back, and — worst of all — moves authorization into the prompt, where it cannot be enforced.Tool orchestration: five levels and a budget
Tool competence is not binary. Decomposing it into five levels shows that production failures almost never sit at level one.
- Level 1 — single call. Pick the right tool, shape valid arguments.
- Level 2 — fixed sequence. Carry a result through several steps without losing it.
- Level 3 — parallel calls. Several independent reads at once.
- Level 4 — conditional and nested calls. One step's output shapes the schema of the next. This is where most systems break.
- Level 5 — self-correction. Recognise an empty or failed result and re-plan, instead of narrating the error to the user as a fact.
The main lever is the tool surface, not the prompt. A tool description is its contract, and it matters more than model size. Keep roughly 20 tools per agent: beyond that, selection accuracy degrades, and the cure is routing to narrower sub-agents, not a bigger model. Errors must be actionable — code, message, retryable. A 'something went wrong' error guarantees level 5 never happens.
A description is a contract, not documentation. Name tools after the object type they act on (shipment.search, wagon.flag) rather than after the verb alone. Two tools with overlapping descriptions guarantee confusion, and no prompt disentangles them. When the surface grows, split between agents rather than inside one: the router hands a sub-agent an explicitly stated task plus the object references it needs, never a shared reasoning scratchpad — otherwise context grows while accuracy falls.
The budget is architecture, not configuration. A ReAct loop (thought → action → observation) with no ceiling on steps, wall-clock and cost is an outage with a spinner on it. Hitting the ceiling must not produce an invented answer: it must escalate with the accumulated trace attached.
Utility-guided routing. Expected value of a call = P(useful) × value − cost (latency + tokens + money). Cheap deterministic tools — an ontology filter, a point lookup — are ranked first; expensive generative ones last. Parallelise side-effect-free reads only; writes serialise through the replicated log (E8.3).
Measurement. End-answer accuracy hides fragility. Measure separately: tool-selection accuracy, argument validity, and recovery rate after a failed call. An agent at 92% end accuracy with a 40% recovery rate collapses on the first API degradation.
# Bounded ReAct loop with utility-guided tool selection.
# An unbounded agent loop is an outage with a spinner on it.
BUDGET = {'steps': 8, 'seconds': 25.0, 'usd': 0.05}
def expected_utility(tool, trace):
# value of the information, minus what the call actually costs us
return tool.p_useful(trace) * tool.value - (
tool.latency_s * LATENCY_PRICE + tool.usd
)
def run(goal, tools, model, budget=BUDGET):
trace = []
spent = {'steps': 0, 'seconds': 0.0, 'usd': 0.0}
while spent['steps'] < budget['steps']:
# cheap deterministic tools outrank expensive generative ones;
# the slice keeps the visible surface under the selection cliff
allowed = sorted(tools, key=lambda t: -expected_utility(t, trace))[:9]
step = model.plan(goal, trace, allowed)
if step.kind == 'final':
return step.answer, trace
obs = call(step.tool, step.args) # typed result, never raw prose
if obs.error and obs.error.retryable:
# actionable error -> level 5 recovery is possible
trace.append(Observation(obs.error.code, obs.error.message))
else:
trace.append(obs)
spent['steps'] += 1
spent['usd'] += obs.usd
spent['seconds'] += obs.latency_s
if spent['usd'] > budget['usd'] or spent['seconds'] > budget['seconds']:
return escalate(goal, trace), trace # hand over the trace, do not spin
return escalate(goal, trace), trace
In practice
An incident-response agent exposed 34 tools on one flat surface. Splitting it into three routed sub-agents of at most 9 tools each lifted tool-selection accuracy from 71% to 96%, cut median steps from 11 to 6, and reduced cost per incident from $0.84 to $0.31. The model was never changed.The anti-pattern
Exposing a single genericrun_query(sql) tool instead of typed actions. The model writes syntactically valid but semantically wrong SQL, per-action authorization becomes impossible, and tool-selection accuracy cannot be measured at all — there is only one tool.Shared state: consensus, CRDTs and disconnected enclaves
Two different problems get conflated constantly. Agreeing on one ordered log (Raft, Paxos) gives linearizability, requires a quorum, and sacrifices availability under a partition. Converging without coordination (CRDTs) is always writable and merges deterministically, but cannot express the invariant 'there must be exactly one approval'.
Raft in three lines. Leader election with terms, log replication, commit on a majority. To survive f failures you need 2f+1 nodes: five tolerate two. The leader is simultaneously a throughput ceiling and a latency floor (one RTT to the majority). Put things where order matters into it: the agent action log, the ontology version, the policy bundle version.
Paxos or Raft — the same arithmetic. Multi-Paxos and Raft give identical guarantees and the identical majority requirement; Raft wins on implementation clarity and an explicit leader, which makes it easier to operate correctly. What must not go into consensus: high-frequency telemetry, metrics and the agent's intermediate observations. The leader becomes a bottleneck immediately, and nobody needs those events ordered — they belong in a streaming pipeline, not a replicated log.
CRDTs where operations commute. OR-Sets for tags and labels (add-wins with tombstones), LWW registers for triage notes, counters. The critical detail: LWW on wall-clock timestamps with 400 ms of skew silently loses edits. Use a hybrid logical clock — it preserves causality and does not depend on NTP.
Disconnected and air-gapped enclaves. What enters an enclave is a signed bundle: model weights, ontology version, policy bundle, tool manifest — each content-hashed and signature-verified on load. What leaves through the one-way channel is a signed delta and nothing else.
- The enclave runs its own Raft group, never one shared with the core. A single group spanning the air gap means any disconnection stalls writes for the entire fleet.
- Local commits are typed
local_commitwith authority scope local. An enclave committing 'globally authoritative' decisions on its own quorum is split-brain with better branding. - Reconnection is a merge, not a push. CRDT state merges on its own. Logged actions are replayed as proposals and re-validated against the current policy version, because policy may have changed while the enclave was dark.
- Version skew. Every action carries an
ontology_version; on mismatch, reject with a code — never silently coerce.
# fleet.yaml - one core Raft group plus disconnected enclaves with CRDT state
core:
consensus: raft
members: [core-1, core-2, core-3, core-4, core-5] # 2f+1 -> tolerates f=2
linearizable_state:
- action_log # order matters: who approved what, and first
- ontology_version
- policy_bundle_version
enclaves:
- id: fwd-17
connectivity: intermittent # observed dark window: 6-72h
consensus: raft # SEPARATE group - never spans the air gap
members: [fwd-17a, fwd-17b, fwd-17c]
authority_scope: local # commits are 'local_commit', never global
convergent_state: # coordination-free, always writable
annotations: or-set # add-wins, tombstoned removes
triage_notes: lww-register
clock: hybrid-logical # NOT wall clock: 400ms skew loses edits
bundle: # the only thing that crosses the diode, one way
pins:
ontology_version: 2026.31.0
policy_bundle: sha256:9f3c1d...
model: local-8b-q4:sha256:1a77e0...
signature: minisign
verify_on_load: true # refuse to start on a hash mismatch
reconnect:
annotations: merge # CRDT: deterministic, no human in the loop
actions: replay_as_proposals # re-validated against the CURRENT policy
on_version_skew: reject # never silently coerce a stale action
In practice
A logistics deployment across 40 forward nodes with 6–72 h offline windows: a 5-node Raft group in the core plus a per-node CRDT annotation layer. On reconnection, 18,400 annotations merged in 40 seconds with zero manual conflict resolution, while 260 write actions were replayed as proposals — 11 of which were rejected by a policy engine that had been revised while the nodes were dark.The anti-pattern
Stretching one Raft group across the air gap and relying on wall-clock LWW between nodes with 400 ms of skew. The first stalls fleet-wide writes on every disconnection; the second silently loses edits, and you find out during an audit.Sources this area derives from
- Agentic Reasoning for Large Language Mod
- A Survey on Large Language Model based A
- A Relational Neural Network Database Mod
- ReAct Synergizing Reasoning and Acting in Language
- ETOM A Five Level Benchmark for Evaluating Tool Orchestrat
- The Evolution of Tool Use in LLM Agents From Single Tool C
- Utility Guided Agent Orchestration for Efficient LLM Tool
- Reinforcement Learning for LLM based Multi Agent Systems t
- A Survey on Consensus Algorithms in Bloc
- A Deep Dive into Blockchain Consensus Pr
- A Security Reference Architecture for Bl
- A comparative study on consensus mechani
Work through it interactively
Every area has questions, spaced-repetition cards and a progress record. Those need an account, which is free and takes a moment.
Open the interactive track Create a free account