AI Architect Trainer Open the interactive track

HomeEnterprise 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.

Last reviewed: 2026-09-04 · Українською

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

E8.1

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.

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

E8.2

Tool competence is not binary. Decomposing it into five levels shows that production failures almost never sit at level one.

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 generic run_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

E8.3

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.

# 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

  1. Agentic Reasoning for Large Language Mod
  2. A Survey on Large Language Model based A
  3. A Relational Neural Network Database Mod
  4. ReAct Synergizing Reasoning and Acting in Language
  5. ETOM A Five Level Benchmark for Evaluating Tool Orchestrat
  6. The Evolution of Tool Use in LLM Agents From Single Tool C
  7. Utility Guided Agent Orchestration for Efficient LLM Tool
  8. Reinforcement Learning for LLM based Multi Agent Systems t
  9. A Survey on Consensus Algorithms in Bloc
  10. A Deep Dive into Blockchain Consensus Pr
  11. A Security Reference Architecture for Bl
  12. 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

Continue in this track