diff --git a/src/Persephone.jl b/src/Persephone.jl
index a8becf93d5b91193498441e630f29bc0717e6a2d..96bfece2c8be93670e0d0ce90dfbfc9a03a4ffb5 100644
--- a/src/Persephone.jl
+++ b/src/Persephone.jl
@@ -16,7 +16,7 @@ using
     Agents,
     ArgParse,
     Dates,
-    GeoArrays,
+    GeoArrays, #XXX this is a big dependency - can we get rid of it?
     Logging,
     LoggingExtras,
     Random,
@@ -24,6 +24,7 @@ using
 
 ## define exported functions and variables
 export
+    param,
     simulate,
     initialise,
     stepsimulation!,
@@ -42,9 +43,11 @@ include("core/landscape.jl")
 include("farm/farm.jl")
 include("crop/crops.jl")
 include("nature/nature.jl")
+include("nature/lifehistory.jl")
 include("nature/ecologicaldata.jl")
-include("nature/wolpertinger.jl")
-include("nature/wyvern.jl")
+#include("nature/species/skylark.jl")
+#include("nature/species/wolpertinger.jl")
+#include("nature/species/wyvern.jl")
 include("core/simulation.jl")
 
 end
diff --git a/src/core/landscape.jl b/src/core/landscape.jl
index f8f8fd8dd0ae1704b4e7ed6844119079c09bd0af..573c9511e0e6ffa0ae54d69128397ae567f1c3e7 100644
--- a/src/core/landscape.jl
+++ b/src/core/landscape.jl
@@ -17,6 +17,7 @@ in a single object. The model landscape consists of a matrix of pixels.
 struct Pixel
     landcover::LandCover
     fieldid::Union{Missing, UInt32}
+    events::Vector{Symbol} #TODO implement the rest of the events system
     #FIXME actually this is stupid - I don't want a field ID, I want a field object
 end
 
diff --git a/src/nature/ecologicaldata.jl b/src/nature/ecologicaldata.jl
index f01ead1be6cffc293493c4012a61035369e07699..c387eb2baf79ea800d164a1b69659222cec4a569 100644
--- a/src/nature/ecologicaldata.jl
+++ b/src/nature/ecologicaldata.jl
@@ -6,6 +6,18 @@
 const POPFILE = "populations.csv"
 const INDFILE = "individuals.csv"
 
+"""
+    initecologicaldata()
+
+Create output files for each data group collected by the nature model.
+"""
+function initecologicaldata()
+    newdataoutput(POPFILE, "Date;Species;Abundance", savepopulationdata,
+                  param("nature.popoutfreq"))
+    newdataoutput(INDFILE, "Date;ID;X;Y;Species;Sex;Age;Energy",
+                  saveindividualdata, param("nature.indoutfreq"))
+end
+
 """
     savepopulationdata(model)
 
diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl
new file mode 100644
index 0000000000000000000000000000000000000000..a8e70c00fe8c0b54563a7ca41f2008e6ae6d9c22
--- /dev/null
+++ b/src/nature/lifehistory.jl
@@ -0,0 +1,12 @@
+### Persephone - a socio-economic-ecological model of European agricultural landscapes.
+###
+### This file contains a set of utility functions needed by species.
+###
+
+#TODO initrandompopulation
+
+#TODO killanimal!
+
+#TODO reproduce!
+
+#TODO parentsalive?
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 6610e6206f6160355224d78174f038e4bd7d75eb..b39fb82fb653574fc1742623a044ed8d2d45875c 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -6,78 +6,46 @@
 ## An enum used to assign a sex to each animal
 @enum Sex hermaphrodite male female
 
-"""
-    Species
-
-Species are differentiated by their name, and the functions used to
-initialise their population and update each individual.
-"""
-struct Species
-    name::String
-    initpop!::Function # takes one argument: model
-    update!::Function # takes two arguments: animal, model
-    traits::Dict{String,Any} # a collection of species-specific traits
-end
-#TODO convert species into dicts
-
 """
     Animal
 
 This is the generic agent type for all animals. Species are differentiated
-by the `species` struct passed by them during initialisation.
+by trait dictionaries passed by them during initialisation.
 """
-
+#XXX fix the @agent docstring issue (https://github.com/JuliaDynamics/Agents.jl/issues/715)
 @agent Animal GridAgent{2} begin
-    species::Species
+    #XXX is it (performance-)wise to use a dict for the traits?
+    # Doesn't this rather obviate the point of having an agent struct?
+    traits::Dict{String,Any}
     sex::Sex
     age::Int32
-    energy::Int32
 end
 
-# This dict stores the definitions for all species that can be simulated.
-# (The definitions are created in separate files and registered here.)
-##TODO this needs to be rewritten to deal with the new @species macro
-let specieslist = Dict{String, Species}()
-    """
-        registerspecies(species)
-
-    This function has to be called by every species definition file
-    in order for the species to be used.
-    """
-    global function registerspecies(s::Species)
-        specieslist[s.name] = s
-    end
-
-    """
-        getspecies(name)
 
-    Returns the definition struct for the named species.
-    """
-    global function getspecies(name::String)
-        specieslist[name]
+### MACROS IMPLEMENTING THE DOMAIN-SPECIFIC LANGUAGE FOR DEFINING SPECIES
+
+macro species(name, body)
+    funcname = "speciesfunction_"*name
+    quote
+        Core.@__doc__ function $(esc(funcname))()
+            $(esc(:name)) = string($(QuoteNode(name)))
+            $(esc(body))
+            vardict = Base.@locals
+            speciesdict = Dict{String,Any}()
+            for k in keys(vardict)
+                speciesdict[string(k)] = vardict[k]
+            end
+            return speciesdict
+        end
     end
 end
 
-"""
-    newspecies(properties...)
-
-A utility function to create and register a new species.
-All function arguments are passed on to Species().
-"""
-function newspecies(properties...)
-    registerspecies(Species(properties...))
+macro phase(name, body)
+    :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end)
 end
 
-"""
-    trait(animal, trait)
 
-A utility function to return a trait value for this animal's species.
-Returns nothing if the trait is not defined.
-"""
-function trait(animal::Animal, trait::String)
-    !(trait in keys(animal.species.traits)) && (return nothing)
-    return animal.species.traits[trait]
-end
+### FUNCTIONS INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
 
 """
     stepagent!(animal, model)
@@ -86,11 +54,7 @@ Update an animal by one day.
 """
 function stepagent!(animal::Animal, model::AgentBasedModel)
     animal.age += 1
-    animal.species.update!(animal,model)
-    if animal.energy <= 0
-        @debug "$(animal.species.name) $(animal.id) has died."
-        kill_agent!(animal, model)
-    end
+    animal.traits[animal.traits["phase"]](animal,model)
 end
 
 """
@@ -101,11 +65,10 @@ Initialise the model with all simulated animal populations.
 function initnature!(model::AgentBasedModel)
     # The config file determines which species are simulated in this run
     for s in param("nature.targetspecies")
-        getspecies(s).initpop!(model)
+        funcname = Symbol("speciesfunction_"*s)
+        species = @eval $funcname()
+        species["initialise!"](model)
     end
     # Initialise the data output
-    newdataoutput(POPFILE, "Date;Species;Abundance", savepopulationdata,
-                  param("nature.popoutfreq"))
-    newdataoutput(INDFILE, "Date;ID;X;Y;Species;Sex;Age;Energy",
-                  saveindividualdata, param("nature.indoutfreq"))
+    initecologicaldata()
 end
diff --git a/src/nature/species.jl b/src/nature/species.jl
deleted file mode 100644
index 9cef46320f807d64beadaca1e137e89fd0512758..0000000000000000000000000000000000000000
--- a/src/nature/species.jl
+++ /dev/null
@@ -1,35 +0,0 @@
-### Persephone - a socio-economic-ecological model of European agricultural landscapes.
-###
-### This file implements the macros needed for the species DSL.
-###
-
-#XXX configure species traits via a separate TOML file?
-
-##TODO replace the current registerspecies()
-function registerspecies(speciesdict)
-    println(speciesdict)
-    processeddict = Dict{String,Any}()
-    for k in keys(speciesdict)
-        processeddict[string(k)] = speciesdict[k]
-    end
-    processeddict["testphase"]()
-end
-
-##TODO docstring
-macro species(name, body)
-    speciesfun = gensym()
-    quote
-        Core.@__doc__ $speciesfun = function()
-            $(esc(:name)) = string($(QuoteNode(name)))
-            $(esc(body))
-            return Base.@locals
-        end
-        registerspecies($speciesfun())
-    end
-end
-
-##TODO docstring
-macro phase(name, body)
-    :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end)
-    #:($(esc(name)) = function() $body end) #for testing
-end
diff --git a/src/nature/wolpertinger.jl b/src/nature/species/wolpertinger.jl
similarity index 98%
rename from src/nature/wolpertinger.jl
rename to src/nature/species/wolpertinger.jl
index f22abc6a8b14089224de464bf64c5079304702f5..33a73e37f8609434e3c46fe6373152ac03501d0b 100644
--- a/src/nature/wolpertinger.jl
+++ b/src/nature/species/wolpertinger.jl
@@ -5,6 +5,8 @@
 ### Although I dare say the Wolpertinger is probably rather endangered...
 ###
 
+##TODO convert to @species syntax
+
 """
     initwolpertinger!(model)
 
diff --git a/src/nature/wyvern.jl b/src/nature/species/wyvern.jl
similarity index 98%
rename from src/nature/wyvern.jl
rename to src/nature/species/wyvern.jl
index 13a364dd800b42b8aca302f0c4e2681e9a3d85b2..2c96df93626590fda6216cd54b6fb6355b373d01 100644
--- a/src/nature/wyvern.jl
+++ b/src/nature/species/wyvern.jl
@@ -5,6 +5,8 @@
 ### Thankfully, wyverns are not a species we have to manage for...
 ###
 
+##TODO convert to @species syntax
+
 """
     initwyvern!(model)