# How to Integrate Domain Objects with Embabel Agents: A Complete Guide

> Integrate domain objects with Embabel agents easily. Register instances, use blackboard dependency injection, and annotate methods with @Tool for direct LLM business logic invocation. Explore our complete guide.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: how-to-guide
- Published: 2026-08-09

---

**Integrate domain objects with Embabel agents by registering instances via `AgentBuilder.withToolObject()`, placing them on the blackboard for dependency injection, and annotating callable methods with `@Tool` so the LLM can invoke your business logic directly.**

Domain objects serve as the bridge between your business logic and LLM-driven agents in the Embabel framework. In `embabel/embabel-agent`, these objects function as more than simple data carriers—they expose behavior, resolve from the shared blackboard context, and drive planning decisions. This guide demonstrates how to integrate domain objects with Embabel agents using the `AgentBuilder` API and the `DefaultActionMethodManager` resolution mechanism.

## Architecture Overview

Domain objects connect to the agent runtime through three core components that handle registration, storage, and resolution.

### The Agent Builder and Tool Registration

The `AgentBuilder` constructs the agent graph and registers tools via `withToolObject(domainInstance)`. This method makes your domain instance available to the agent runtime as both a callable tool and a blackboard entry. According to the framework documentation in `Domain page.adoc` (line 128), only methods explicitly exposed through this mechanism become visible to the LLM.

### The Blackboard as Shared Context

The **Blackboard** acts as a shared mutable context that actions read from and write to during execution. When you place domain objects on the blackboard using `blackboard.put(obj)`, they become discoverable for planning and available for automatic injection into action parameters.

### Parameter Resolution via DefaultActionMethodManager

The `DefaultActionMethodManager` resolves method parameters when actions are invoked. As implemented in [`DefaultActionMethodManager.kt`](https://github.com/embabel/embabel-agent/blob/main/DefaultActionMethodManager.kt) (lines 170-197), this component detects parameters annotated with or typed as domain objects and pulls matching instances from the blackboard. If no matching object exists, the framework gracefully injects `null` rather than throwing an exception.

## Step-by-Step Integration

Follow these steps to wire your business logic into the Embabel agent runtime.

### 1. Define a Domain Class with Exposed Methods

Create a class that encapsulates both state and behavior. Annotate any methods you want the LLM to invoke with `@Tool`.

```kotlin
// src/main/kotlin/com/example/Order.kt
import com.embabel.agent.api.annotation.Tool

data class Order(
    val id: String,
    var status: String,
    val items: List<String>
) {
    /** Exposed to the LLM when the object is added as a tool. */
    @Tool
    fun ship() = apply { status = "shipped" }
}

```

### 2. Register the Instance and Populate the Blackboard

Instantiate your domain object and register it with the `AgentBuilder`. The builder automatically places tool objects on the blackboard, making them available for parameter resolution.

```kotlin
val order = Order("ORD-001", "pending", listOf("widget", "gadget"))
val agent = AgentBuilder()
    .withToolObject(order)               // registers as a tool
    .blackboard.put(order)               // ensures discoverability for planning
    .build()

```

### 3. Consume Domain Objects in Actions

Write actions that accept domain objects as parameters. The framework resolves these automatically from the blackboard based on type.

```kotlin
import com.embabel.agent.api.annotation.Action
import com.embabel.agent.api.annotation.Domain

@Action
fun fulfillOrder(@Domain order: Order) {
    order.ship()               // business logic execution
}

```

The `@Domain` annotation (or implicit domain typing) instructs `DefaultActionMethodManager` to resolve the `order` argument from the blackboard entries, as validated in `CostAnnotationJavaTest` (lines 75-105).

### 4. Control Method Visibility

By default, all public methods annotated with `@Tool` are exposed to the LLM. To keep an object on the blackboard for internal actions while hiding its methods from the planning engine, use the exposure flag:

```kotlin
AgentBuilder()
    .withToolObject(myObj, expose = false)  // blackboard-only, no LLM access
    .build()

```

## Practical Implementation Example

This complete example demonstrates a customer loyalty domain model integrated with an agent, mirroring the validation patterns found in `SemanticsAnnotationJavaTest` (lines 191-205).

```kotlin
// ---------------------------------------------------------------
// 1️⃣ Domain object with business logic
// ---------------------------------------------------------------
package com.example.domain

import com.embabel.agent.api.annotation.Tool

data class Customer(
    val id: String,
    var loyaltyPoints: Int = 0
) {
    @Tool               // ← visible to the LLM
    fun addPoints(points: Int) = apply { loyaltyPoints += points }
}

// ---------------------------------------------------------------
// 2️⃣ Agent wiring and registration
// ---------------------------------------------------------------
import com.embabel.agent.builder.AgentBuilder

fun main() {
    val customer = Customer("CUST-42")
    val agent = AgentBuilder()
        .withToolObject(customer)   // registers Customer as a tool
        .build()

    // The LLM can now call customer.addPoints(10) or any action
    // that accepts a Customer parameter.
    agent.run()
}

// ---------------------------------------------------------------
// 3️⃣ Action that consumes the domain object
// ---------------------------------------------------------------
import com.embabel.agent.api.annotation.Action
import com.embabel.agent.api.annotation.Domain

class RewardService {
    @Action
    fun grantWelcomeBonus(@Domain customer: Customer) {
        // `customer` is resolved from the blackboard automatically
        // based on DefaultActionMethodManager resolution logic
        customer.addPoints(100)
    }
}

```

## Key Implementation Files

Understanding these source files provides deeper insight into the domain object lifecycle:

- **[`DefaultActionMethodManager.kt`](https://github.com/embabel/embabel-agent/blob/main/DefaultActionMethodManager.kt)** (lines 170-197): Contains the core resolution logic that injects domain objects from the blackboard into action parameters.

- **`Domain page.adoc`** (lines 86-143): Conceptual documentation explaining domain object purpose, blackboard interaction, and visibility rules.

- **[`CostAnnotationJavaTest.java`](https://github.com/embabel/embabel-agent/blob/main/CostAnnotationJavaTest.java)** (lines 75-105): Demonstrates null-handling and injection of domain objects into cost calculation methods.

- **[`SemanticsAnnotationJavaTest.java`](https://github.com/embabel/embabel-agent/blob/main/SemanticsAnnotationJavaTest.java)** (lines 191-205): Validates nested domain-type properties and metadata handling.

- **[`PackageVisibleTests.java`](https://github.com/embabel/embabel-agent/blob/main/PackageVisibleTests.java)** (line 136): Tests visibility rules for domain classes on the blackboard.

- **[`README.md`](https://github.com/embabel/embabel-agent/blob/main/README.md)** (lines 791-811): High-level description of the Domain model and its architectural role in the framework.

## Summary

- **Define** domain classes containing both state and behavior, annotating LLM-callable methods with `@Tool`.
- **Register** instances using `AgentBuilder.withToolObject()` to expose them as tools to the planning engine.
- **Populate** the blackboard with your domain objects to enable automatic dependency injection during action execution.
- **Resolve** domain objects in action methods via type hints or `@Domain` annotations; `DefaultActionMethodManager` handles the wiring automatically.
- **Handle** missing objects gracefully—unresolved domain parameters receive `null` rather than causing runtime exceptions.

## Frequently Asked Questions

### How does Embabel resolve domain objects when an action is called?

The `DefaultActionMethodManager` inspects action method signatures and automatically injects matching instances from the blackboard. According to the source code in [`DefaultActionMethodManager.kt`](https://github.com/embabel/embabel-agent/blob/main/DefaultActionMethodManager.kt) (lines 170-197), the system checks parameter types against blackboard entries and resolves the appropriate object, or `null` if no match exists.

### Can I prevent the LLM from calling certain methods on my domain object?

Yes. Only methods annotated with `@Tool` are exposed to the LLM. You can also pass `expose = false` to `withToolObject()` to keep the object available on the blackboard for internal actions while completely hiding its methods from the planning engine, as documented in `Domain page.adoc` (line 128).

### What happens if a domain object is not found on the blackboard during action execution?

The parameter resolves to `null`. The framework gracefully handles missing domain objects by injecting `null` into the action method parameter, allowing you to implement null-safe logic or conditional checks within your action, validated by test cases in `CostAnnotationJavaTest`.

### Are domain objects only for data, or can they contain business logic?

Domain objects in Embabel are designed to carry both state and behavior. Unlike simple DTOs, they can contain methods that modify internal state—such as `ship()` or `addPoints()`—which the LLM can invoke directly when properly annotated with `@Tool`, enabling rich domain models within your agent architecture.