Deck 12 (Aguzzi, "AI-Engineering: Tools, Agents and Verification") opens with a guiding question that structures the whole chapter: "how can a language model perceive, decide, act, remember, and remain verifiable?" The historical motivation is precise: "modern agentic AI emerges when classical agent (like reinforcement learning) theory meets foundation-model capabilities. Classical AI already gave us the vocabulary of agents, environments, actions, and goals — previous lessons on reinforcement learning and Markov decision processes introduced these concepts in a formal way :)" — the connection to chapter 13 is explicit on the first slide. "Modern LLMs add a practical interface: they can interpret natural-language tasks and coordinate multi-step workflows."
This creates three engineering questions, which map exactly onto the three parts of the chapter's title:
The reference lens throughout is LangChain4j, "to discuss these ideas at the application level" — a Java/Scala framework whose abstractions (AI Services, tools, ChatMemory, agents, verification helpers) let the lecture talk about architecture instead of plumbing. The closing lesson of the deck, "power without verification is not engineering", is the same thesis that opened the course in chapter 1: validation is pervasive, and it applies to agents too.
The deck's working definition is deliberately classical:
"An agent is a system that perceives an environment, selects actions, and executes them in pursuit of explicit objectives."
Three terms carry the load: "perception provides information about the current state of the world (or of the task)"; "action modifies the environment, the system state, or the available knowledge"; "objectives define the criterion by which one action is preferred over another — in LLM-based agents, objectives are often expressed as natural-language instructions". The key point of the slide: "agency is defined by the full perception-action loop, not by intelligence alone" — a model that only generates text is not an agent; a model embedded in a loop that perceives, decides and acts is.
The four core properties tell us "what kind of system we are building":
The RL echo is deliberate: the perception-action loop is the agent-environment interface of chapter 13, section 3, goal-directedness is the reward hypothesis, temporality is the episode. What changed is the reasoning core: where the RL agent learned a policy from rewards, the LLM agent interprets objectives in natural language and plans multi-step actions — and both face the same engineering problem of abstraction ("the number of states and actions reasonably limited") and of verification.
"An agentic system works only when model, tools, memory, and orchestration remain aligned." The deck's architecture has four layers:
LangChain4j's first high-level abstraction above raw chat is the AI Service: "a declarative Java interface whose methods are implemented by an LLM-backed runtime". "The application invokes a method, while the framework handles prompt construction, model invocation, and result mapping"; "optional capabilities such as tools, memory, and retrieval can be attached to the same abstraction". The canonical example — a sentiment analyser with a typed boolean return:
trait SentimentAnalyzer:
// The @UserMessage annotation specifies the prompt template for this method.
@UserMessage(Array("Does {{it}} has a positive sentiment?"))
def analyzeSentiment(text: String): Boolean
object SentimentAnalyzer:
def createWith(llmModel: ChatModel): SentimentAnalyzer =
AiServices.builder(classOf[SentimentAnalyzer])
.chatModel(llmModel)
.build()
def testSentimentAnalyzer(): Unit =
val model = OllamaChatModel.builder().baseUrl("http://localhost:11434")
.modelName("gemma4:e2b")
.build()
val sentimentAnalyzer = SentimentAnalyzer.createWith(model)
sentimentAnalyzer.analyzeSentiment("Scala is a great programming language!") // true
Read it as an interface-driven design: the application declares what it wants (a function from String to Boolean) and the framework supplies how (prompt construction, model call, result mapping). This is the "declarative over procedural" spirit of the whole course — from the DSLs of chapter 9 to the aggregate programs of chapter 14.
"Tools turn language into action." A tool is "a callable resource outside the model, exposed through a well-defined interface" — "a tool is not stored inside the model parameters; it is made available by the surrounding software system". The minimum contract has four parts:
Why tools matter: grounding — "tools can reduce hallucination by connecting answers to external information sources: before replying, the model can check facts, compute results, or query databases instead of relying solely on internal knowledge (self-augmentation and self-correction patterns)"; capability extension — "tools enable actions that plain text generation cannot perform reliably on its own (e.g. performing calculations, or manipulating structured data)"; system interaction — "tools let the agent read from and write to real systems"; observability — "tool schemas make behavior easier to inspect and evaluate".
"A good tool specification acts as a contract between the model and the execution layer... better tool contracts usually produce more reliable tool selection and safer execution." The LangChain4j calculator shows the pattern — @Tool annotations generate the JSON schema:
class Calculator:
@Tool(name = "sum", value = Array("Calculate the sum of two numbers"))
def sum(@P("left value") a: Double, b: Double): Double = a + b
@Tool(name = "subtract", value = Array("Calculate the difference of two numbers"))
def subtract(a: Double, b: Double): Double = a - b
@Tool(name = "multiply", value = Array("Calculate the product of two numbers"))
def multiply(a: Double, b: Double): Double = a * b
@Tool(name = "divide", value = Array("Calculate the quotient of two numbers"))
def divide(a: Double, b: Double): Double = a / b
trait MathAgent:
@UserMessage(Array("I need to perform this calculation: {{expression}}"))
def calculate(expression: String): Double
@main def testMathAgent(): Unit =
val model = //...
val mathAgent = AiServices.builder(classOf[MathAgent]).chatModel(model)
.addTool(new Calculator).build()
mathAgent.calculate("What is the sum of 5 and 7?") // 12.0
How does language become action? "The orchestration layer converts language into execution and execution back into context": the agentic system must "describe available tools to the model, interpret the model's action request, execute the selected tool, return the result as a new observation". The historical baseline was zero-shot tool use: "tools described directly in the prompt, the model asked to emit a textual pseudo-command — limitation: formatting errors became execution errors". Current practice: "many modern models already support this ReAct-style loop natively — they decide when a tool is needed, emit a structured request, observe the result, and continue reasoning". "Function calling is the usual interface: the model returns a typed function name with validated arguments instead of fragile free text"; "frameworks such as LangChain4j help normalize provider-specific tool-calling APIs at the application level".
Finally, the Model Context Protocol (MCP) addresses integration fragmentation: "traditional integrations (files, Slack, GitHub) require custom point-to-point glue code, leading to fragmentation and duplicate effort"; MCP is "a standardized open protocol to securely connect AI models to any data source or tool — build once, use everywhere". Architecture: "MCP Client: your AI application (e.g. LangChain4j) requesting tool execution; MCP Server: standalone process exposing dynamic schemas (e.g. read_file, write_file)"; "the client discovers tools dynamically at startup via a handshake; communicates via local processes (Stdio) or remote hosts (Streamable HTTP)". The deck's verdict: "conceptually it just a simple way to decouple the model's tool-calling interface from the actual implementation of tools."
"Memory turns interaction into continuity": memory is "the mechanism by which an agent stores, retrieves, and reuses information across reasoning steps or across interactions". The first distinction of the lecture is Memory ≠ History: "History: a raw, passive, chronological transcript of what happened. Memory: an active, curated subset injected into the context to shape the reasoning process." The four pillars of memory: continuity (conversation context across turns and steps), personalization (user preferences, instructions, settings), planning (task state, subgoals, execution progress), retrieval (contextually injecting relevant prior or external knowledge).
The two lifecycles:
ChatMemory, customisable with policies: eviction ("selectively dropping specific message types, e.g. redundant tool outputs"), compression ("summarizing older turns to preserve context while saving tokens"), filtering ("removing irrelevant or outdated intermediate reasoning steps"). A sliding window is one line: MessageWindowChatMemory.withMaxMessages(10). Multi-user isolation uses @MemoryId: "for concurrent users, memory must be isolated per session" — LangChain4j routes interactions to per-session memories via chatMemoryProvider(sessionId -> ...).RAG splits into two stages. Indexing (offline): "documents are cleaned, parsed, and split into smaller segments (chunking — cuts long texts into cohesive segments fitting the context window); each segment is converted into a numeric vector (embedding — text into numeric coordinates representing semantic concepts) and stored". In LangChain4j, FileSystemDocumentLoader.loadDocuments("/docs") + an in-memory EmbeddingStore + EmbeddingStoreIngestor handle "segment splitting, embedding generation, and database storage" automatically. Retrieval (online): "the query is vectorized using the same embedding model; a specialized vector store (Vector Store: database optimized for high-speed semantic similarity searches) finds and returns semantically similar segments; relevant segments are injected directly into the LLM prompt context" — in code, an EmbeddingStoreContentRetriever bound to the AI Service via contentRetriever(retriever) with maxResults(3). "For both stages, the choice of embedding model (semantic depth), chunking strategy (segment size), and vector store (retrieval architecture) directly determines retrieval precision and relevance."
With tools and memory in place, the question is orchestration. The deck uses Anthropic's taxonomy of two paradigms:
The practical perspective: "start with workflows, introduce pure agency only for high-complexity, unstructured decisions".
In LangChain4j, an agent is "defined as an interface (similar to an AI Service)" with the @Agent annotation; subagents "can write results to a shared state and read input from previous steps". The AgenticScope is "a stateful blackboard containing data shared among the subagents participating in an agentic system": a shared blackboard ("agents read required inputs from the scope and write results back using outputKey"), an automatic registry ("automatically logs the exact sequence of agent invocations and their raw outputs" — the observability that verification needs), and persistence & recovery ("the scope can be serialized, persisted, and reloaded to recover a multi-turn process from failure"). "In practice: the scope decouples agent execution from orchestration routing."
The workflow builders cover the taxonomy:
AgenticServices.sequenceBuilder().subAgents(creativeWriter, audienceEditor).outputKey("editedStory").build();loopBuilder().subAgents(styleScorer, styleEditor).maxIterations(5).exitCondition(scope => scope.readState("score", 0.0) >= 0.8);parallelBuilder().subAgents(foodExpert, movieExpert).executor(...) — with the safety note that "subagents in parallel mappers cannot have ChatMemory" (concurrency and shared mutable memory do not mix);conditionalBuilder().subAgents(scope => scope.readState("category") == "MEDICAL", medicalExpert)...;supervisorBuilder().chatModel(plannerModel).subAgents(withdrawAgent, creditAgent, exchangeAgent).responseStrategy(SupervisorResponseStrategy.SUMMARY) — "Transfer 100 EUR from Mario to Georgios". Response strategies: LAST ("the response of the last executed subagent", default), SUMMARY ("the supervisor's transactional summary of operations"), SCORED ("an LLM scorer chooses the best response between the two"). Context policies guide the planner: supervisorContext("Policies: Prefer internal tools, currency is USD"), overridable per invocation."Power without verification is not engineering." Why traditional assertions fail: "agent loops are multi-step, stateful, and non-deterministic"; "unit testing assumes deterministic, reproducible state-to-state mappings"; "models behave as black-boxes with probabilistic output spaces"; "small changes in prompts can cause catastrophic, silent regressions"; "dialogue histories and intermediate tool responses create an infinite state space"; and "error propagation (cascading failures): a minor reasoning or tool-call error in step i propagates and amplifies through subsequent steps". The conclusion: "we must evaluate the entire trace of the interaction, not just the final output".
The deck's answer is a 3-stage evaluation loop running "continuously at different granularities and speeds":
pass@k = E[1 − C(n−c,k)/C(n,k)]". Generalisation: "an LLM-as-a-Judge can act as the automated validator to compute pass@k on semantic or free-text generation". The temperature trade-off: "for pass@1, use low temperature (0.0–0.2) to minimize bad paths; for high k (e.g. pass@10), use high temperature (0.7+) to maximize sample diversity, increasing the probability that at least one candidate passes".Verifying the trajectory — "in multi-step agents, the path taken is as important as the final answer": tool selection accuracy (classification metrics Precision, Recall, F1 comparing expected vs actual tool-call traces); argument boundary checking ("validates that arguments emitted by the model fall within safe, expected ranges and schemas — preventing SQL/command injection"); observation grounding ("is the agent's next reasoning step logically coherent with the tool's output, or does it ignore facts/hallucinate past them?"); exception recovery (self-correction) ("inject simulated tool exceptions — API timeouts, rate limits — to verify if the agent's planner gracefully recovers, falls back to other tools, or reports failure cleanly"). The closing reminder: "a correct answer reached through an inefficient or unsafe tool loop is still an engineering failure."
This is the last chapter, and the deck's own conclusion points beyond itself: "evaluating multi-step reasoning requires accounting for stochasticity, non-determinism, and complex tool trajectories"; "key dimensions for real-world deployment: model versioning (track behavioral changes over time to establish robust LLMOps pipelines), advanced observability (trace analysis tools like LangSmith), robust verification (simulation environments, synthetic datasets, and adversarial testing)". The frameworks it names — LangSmith, MCP, A2A (agent-to-agent communication) — are where the field is going.
Standing at the end of the course, the four parts assemble into one argument:
The explicit bridges built along the way: Alchemist as the simulator of both chemical systems and aggregate programs (chapters 12 → 14); Q-learning learning policies inside simulations and for device parameters (chapters 12, 13 → 14); the reward hypothesis of chapter 13 as the goal-directedness of chapter 15's agents; the MDP vocabulary of chapter 13 as the formal grounding of the agent definition of this chapter; and the pervasive-validation thread from chapter 1, through model checking in chapter 11, to pass@k and LLM-as-a-judge here. One thread throughout, as the course index promised: rigorous specification, then pervasive validation of it.
Chapter 1 taught that validation is pervasive: every artifact — code, model, policy — earns its place by being checked. Chapter 15 applies the same rule to the newest artifact of the course, the LLM agent: not "does it answer correctly" but "does it perceive, decide, act, remember, and does the whole trace stand up to inspection?" The three-stage loop (deterministic filters, semantic validation, production telemetry) is the pervasive-validation pyramid of chapter 1, rebuilt for probabilistic, multi-step, tool-using systems. The course's one thread — rigorous specification, then pervasive validation — holds from the first test to the last judge.
The lab deck (12-Lab) sums up: "agents: autonomous entities that perceive their environment, make decisions, and act to achieve goals; in the generative AI era, agents leverage LLMs/SLMs for reasoning and planning — tools: enable agents to interact with the external world (e.g. APIs, databases, and files); memory: stores short-term conversation context or long-term facts; coordination: complex tasks can be split across multiple specialized sub-agents. Verification and monitoring of agents: why? to ensure reliability, safety, correctness, and alignment with user intent; how? by evaluating tool traces, analyzing LLM reasoning (thought paths), and using programmatic assertions or other LLMs as judges; human-in-the-loop: monitoring execution loops to support user validation and feedback."
asmd-public-12-agentic-ai-code, powered by LangChain4j and langchain4j-agentic) and import it in IntelliJ as an SBT project. Install and run Ollama locally: pull a reasoning model (ollama pull qwen3.5:4b or gemma4:e2b) and an embedding model (ollama pull ibm/granite-embedding:30m).it.unibo.services, study SentimentAnalyzer and TicTacToe ("how they are defined as tools and used in the AiServices configuration"); in it.unibo.tools, study MathModule and ToolsViaZeroShot — how methods annotated with @Tool are registered and selected by the LLM/SLM.it.unibo.memory, study MemoryExample (conversational context) and RagExample (document ingestion, in-memory vector storage, semantic search retrieval).it.unibo.agents, study multi-agent orchestration, state sharing via AgenticScope, and the strongly typed workflows (Sequential, Loop, Parallel, Conditional, Supervisor); in it.unibo.verification, study Evaluator — "how an LLM-as-a-Judge evaluates model correctness and computes the unbiased pass@k metric using Google Gemini".| Task | What it asks |
|---|---|
| Task 1 — autonomous robot navigation | Guide a robot from (0.0, 0.0) to the target goal at (3.0, 3.0), avoiding obstacles: in RobotTools.scala describe the robot's capabilities (movement, environment status) by annotating methods with @Tool and implementing the environment steps; in RobotSimulationApp.scala implement the RobotAgent trait and configure the OllamaChatModel and AiServices with conversational memory and tools to plan and execute the path — the perception-action loop of section 2, with the robot grid echoing the RL grids of chapter 13. |
| Task 2 — verification and interactive tracing | Programmatically inspect MessageWindowChatMemory in the loop to assert which tools were chosen in the last step; test the agent across alternative grid configurations with different obstacle placements to evaluate navigation robustness — trajectory verification (section 7) on a concrete system. |
| Task 3 — model and temperature comparison | Compare different reasoning models (e.g. qwen3.5:4b vs gemma4:e2b) in terms of navigation success, path length, and tool execution efficiency; experiment with temperature settings (0.0 vs 0.7) to analyze their impact on deterministic path planning and exploration — the pass@k temperature trade-off of section 7 in practice. |
| Task 4 — advanced scenarios (optional) | Enhance the environment to support carrying objects and implement tools for Hold and Release actions; run the other workflow examples in it.unibo.agents (Sequential, Parallel, Loop, Conditional, Supervisor) to see how multi-agent coordination works. |
The strongest closing presentation connects the three chapters of Part D and the course's thread: the agent of this chapter is the RL agent of chapter 13 with an LLM planner and tools (the perception-action loop, formalised by MDPs); its environment can be a simulation from chapter 12 (Alchemist, kinetic Monte Carlo) or an aggregate system from chapter 14 (fields as the state space); and its verification reuses everything the course taught about validation — deterministic checks (chapter 1–3), quantitative reasoning (chapter 11), and human-aligned evaluation (chapters 4–5). If you can walk one system — say, a robot navigating a grid with a learned or planned policy — through all four parts, you have the course.
(1) How can an LLM become the reasoning core of an agent? — the agent definition, core properties and AI Services. (2) How can that agent interact with external systems? — tools (contracts, function calling, MCP) and memory (short/long-term, RAG). (3) How can we verify whether the resulting behavior is correct, safe, and reliable? — the three-stage evaluation loop and trajectory verification.
An agent is a system that perceives an environment, selects actions, and executes them in pursuit of explicit objectives; agency is defined by the full perception-action loop, not by intelligence alone. Properties: situatedness (embedded and effective), autonomy (a spectrum, not binary), goal-directedness (evaluated relative to explicit objectives), temporality (decisions unfold over time).
Reasoning layer (LLM: interpret, plan, synthesise), action layer (tools: search, APIs, databases, code execution), continuity layer (memory), control layer (orchestration: translate model outputs into executable operations and return observations). An AI Service is a declarative Java interface whose methods are implemented by an LLM-backed runtime: the app invokes a method, the framework handles prompt construction, model invocation, and result mapping; tools, memory and retrieval attach to the same abstraction.
Name (identification), description (when to use), input schema (valid arguments), output schema (interpretable results). They matter for: grounding (reduce hallucination by connecting answers to external sources), capability extension (calculations, structured data), system interaction (read/write real systems), observability (schemas make behaviour inspectable).
It must describe available tools to the model, interpret the model's action request, execute the selected tool, and return the result as a new observation. Zero-shot tool use described tools in the prompt and asked for textual pseudo-commands — formatting errors became execution errors. Modern models support the ReAct loop natively: they decide when a tool is needed, emit a structured request (function calling: a typed function name with validated arguments), observe the result, and continue reasoning.
The Model Context Protocol: a standardized open protocol to securely connect AI models to any data source or tool, replacing custom point-to-point glue code. Architecture: MCP Client (the AI application requesting tool execution) and MCP Server (a standalone process exposing dynamic schemas); discovery via a startup handshake; transports via local processes (Stdio) or remote hosts (Streamable HTTP). It decouples the model's tool-calling interface from the actual implementation of tools.
History: a raw, passive, chronological transcript. Memory: an active, curated subset injected into the context to shape reasoning. Pillars: continuity (context across turns and steps), personalization (preferences and instructions), planning (task state, subgoals, progress), retrieval (contextually injecting relevant prior or external knowledge).
Short-term: current conversation scope, injected into the context window, volatile; managed by ChatMemory with eviction, compression and filtering policies, isolated per session via @MemoryId. Long-term: across conversations, stored in external databases/vector stores, durable. RAG: Retrieve (match the query against a vector store), Augment (inject the most relevant chunks into the context), Generate (answer with the injected knowledge). Indexing (offline): clean, chunk, embed, store. Retrieval (online): vectorize the query, top-k similar segments, inject into the prompt.
Workflows: deterministic hardcoded paths (sequences, loops, parallel, conditional branches) — predictable, reliable, testable, rigid. Pure agents: the LLM as autonomous reasoning core and planner deciding the next tool/subagent from state — flexible, adaptive, harder to verify. "Start with workflows, introduce pure agency only for high-complexity, unstructured decisions." Patterns: sequential, loop (maxIterations + exitCondition), parallel (no ChatMemory in mappers), conditional (router), supervisor (LLM planner; response strategies LAST, SUMMARY, SCORED; context policies).
A stateful blackboard containing data shared among subagents: agents read inputs and write results via outputKey; an automatic registry logs the exact sequence of agent invocations and their raw outputs; the scope can be serialized, persisted and reloaded to recover a multi-turn process from failure. It decouples execution from orchestration routing — and its registry is the trace that verification evaluates.
Agent loops are multi-step, stateful and non-deterministic; models are probabilistic black-boxes; small prompt changes cause silent regressions; the state space is infinite; errors cascade. Level 1: unit tests and assertions (deterministic filter, CI/CD, schemas/latency; pass@k). Level 2: semantic validation against a golden dataset with human-aligned LLM-as-a-judge (critique-first prompting, few-shot real alignment, RAGAs triad). Level 3: production telemetry and monitoring (implicit feedback, exception rates, drift, tracing) with failure modes backported into the dataset.
pass@k: fraction of problems solved when generating k candidate solutions (solved if at least one passes verification); unbiased estimator over n samples with c successes: pass@k = 1 − C(n−c,k)/C(n,k); low temperature for pass@1, high for large k. Critique shadowing: binary judgments ("ready for production?") plus a one-sentence critique, instead of biased 1–5 ratings. Trajectory checks: tool selection accuracy (P/R/F1 on traces), argument boundary checking (against injection), observation grounding (reasoning coherent with tool output), exception recovery (simulated failures).