Developer Docs

Cloning the repository

Since we include documentation with many animated gifs and videos in the repository, a standard clone can be larger than expected. If you wish to do any development work, it is better to use

git clone --single-branch

Creating a new space type

Creating a new space type within Agents.jl is quite simple and requires the extension of only 5 methods to support the entire Agents.jl API. The exact specifications on how to create a new space type are contained within the source file: src/core/space_interaction_API.jl.

In principle, the following should be done:

  1. Think about what the agent position type should be. Add this type to the ValidPos union type in src/core/model_abstract.jl.
  2. Think about how the space type will keep track of the agent positions, so that it is possible to implement the function nearby_ids.
  3. Implement the struct that represents your new space, while making it a subtype of AbstractSpace.
  4. Extend random_position(model).
  5. Extend add_agent_to_space!(agent, model), remove_agent_from_space!(agent, model). This already provides access to add_agent!, kill_agent! and move_agent!.
  6. Extend nearby_ids(position, model, r).
  7. Create a new "minimal" agent type to be used with @agent (see the source code of GraphAgent for an example).

And that's it! Every function of the main API will now work. In some situations you might want to explicitly extend other functions such as move_agent! for performance reasons.

Designing a new Pathfinder Cost Metric

To define a new cost metric, simply make a struct that subtypes CostMetric and provide a delta_cost function for it. These methods work solely for A* at present, but will be available for other pathfinder algorithms in the future.


An abstract type representing a metric that measures the approximate cost of travelling between two points in a D dimensional grid.

Pathfinding.delta_cost(pathfinder::GridPathfinder{D}, metric::M, from, to) where {M<:CostMetric}

Calculate an approximation for the cost of travelling from from to to (both of type NTuple{N,Int}. Expects a return value of Float64.


Implementing custom serialization

For model properties

Custom serialization may be required if your properties contain non-serializable data, such as functions. Alternatively, if it is possible to recalculate some properties during deserialization it may be space-efficient to not save them. To implement custom serialization, define methods for the to_serializable and from_serializable functions:


Return the serializable form of the passed value. This defaults to the value itself, unless a more specific method is defined. Define a method for this function and for AgentsIO.from_serializable if you need custom serialization for model properties. This also enables passing keyword arguments to AgentsIO.load_checkpoint and having access to them during deserialization of the properties. Some possible scenarios where this may be required are:

  • Your properties contain functions (or any type not supported by JLD2.jl). These may not be (de)serialized correctly. This could result in checkpoint files that cannot be loaded back in, or contain reconstructed types that do not retain their data/functionality.
  • Your properties contain data that can be recalculated during deserialization. Omitting such properties can reduce the size of the checkpoint file, at the expense of some extra computation at deserialization.

If your model properties do not fall in the above scenarios, you do not need to use this function.

This function, and AgentsIO.from_serializable is not called recursively on every type/value during serialization. The final serialization functionality is enabled by JLD2.jl. To define custom serialization for every occurrence of a specific type (such as agent structs), refer to the Custom Serialization section of JLD2.jl documentation.

AgentsIO.from_serializable(t; kwargs...)

Given a value in its serializable form, return the original version. This defaults to the value itself, unless a more specific method is defined. Define a method for this function and for AgentsIO.to_serializable if you need custom serialization for model properties. This also enables passing keyword arguments to AgentsIO.load_checkpoint and having access to them through kwargs.

Refer to AgentsIO.to_serializable for more info.


For agent structs

Similarly to model properties, you may need to implement custom serialization for agent structs. from_serializable and to_serializable are not called during (de)serialization of agent structs. Instead, JLD2's custom serialization functionality should be used. All instances of the agent struct will be converted to and from the specified type during serialization. For OpenStreetMap agents, the position, destination and route are saved separately. These values will be loaded back in during deserialization of the model and override any values in the agent structs. To save space, the agents in the serialized model will have empty route fields.

OpenStreetMapSpace internals

Details about the internal details of the OSMSpace are discussed in the docstring of OSM.OpenStreetMapPath.


As Agents.jl is developed we want to monitor code efficiency through benchmarks. A benchmark is a function or other bit of code whose execution is timed so that developers and users can keep track of how long different API functions take when used in various ways. Individual benchmarks can be organized into suites of benchmark tests. See the benchmark directory to view Agents.jl's benchmark suites. Follow these examples to add your own benchmarks for your Agents.jl contributions. See the BenchmarkTools quickstart guide, toy example benchmark suite, and the BenchmarkTools.jl manual for more information on how to write your own benchmarks.

Creating a new AgentBasedModel implementation

The interface defined by AgentBasedModel, that needs to be satisfied by new implementations, is very small. It is contained in the file src/core/model_abstract.jl.