8. Reusing agent behaviour

Reuse begins with shared meaning. Agents that exchange a light request must agree on its content, and agents that inherit a goal must agree on what achieving it means. Move common declarations and behavior into a parent agent when that contract is stable.

Share declarations

Create src/main/astra/Lighting.astra:

agent Lighting {
    types lighting {
        formula light(string);
        formula setLight(string);
    }
}

The light predicate describes state. The setLight predicate is message content requesting a change. Other agents can declare agent RoomController extends Lighting and agent LightClient extends Lighting to reuse both signatures.

A parent agent is a reusable class, not an agent automatically created at runtime. Each instantiated child has its own beliefs, modules and execution. Shared declarations do not create shared state between agents.

Inherit behavior deliberately

Inheritance can provide modules and plans as well as formula declarations. Multiple parents let an agent combine reusable capabilities. A child can add more specific plans for an inherited goal, but plan selection still depends on event matching, contexts and inheritance order. Test both the specific case and the inherited fallback rather than treating inheritance as a textual copy operation.

Choose parent classes that have a clear responsibility. A parent containing formula declarations is easy to share between a controller and client; a parent that starts a device, creates agents and exits the runtime has much stronger lifecycle assumptions.

Prefer shared goal contracts to exposing implementation details. A client should request a light result without needing to know whether the controller uses a Java module, an EIS environment or a CArtAgO artifact.

Invoke goals represented as data

Ordinary !goal(...) calls are the clearest choice when the goal is known in the program. Dynamic invocation is useful when the requested goal arrives as data or is selected by another component.

The Goals module parses a textual goal into an astra.core.GoalInvocation. ASTRA’s invoke statement executes it as a native subgoal. The caller suspends until it completes, and can read typed, named outputs afterward. Each invocation executes once; create a new invocation for a new call.

An @Callable annotation describes a goal’s discoverable contract: its description, input parameters and output parameters. It does not bypass plan contexts or guarantee a particular plan will be selected. Nor does it publish an external service automatically. The Callable Goals page contains a complete dynamic-call example and the annotation requirements.

Test the boundary

For inherited behavior, test what the child promises to its callers rather than the private sequence of actions in a parent. For a dynamic goal, test malformed input, unmatched signatures and output types as well as a successful invocation. A component using metadata must still handle normal goal failure.

Next: Building cooperating agents.

Further Details

For a complete example of multiple inheritance, see String Assembler. It combines generation, assembly and printing behaviours in one agent, then reuses them in two cooperating-agent designs.