diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl
index a8e70c00fe8c0b54563a7ca41f2008e6ae6d9c22..e80a1d9252aee85c2f5f6ddfb180264ef11157b2 100644
--- a/src/nature/lifehistory.jl
+++ b/src/nature/lifehistory.jl
@@ -1,12 +1,59 @@
 ### Persephone - a socio-economic-ecological model of European agricultural landscapes.
 ###
-### This file contains a set of utility functions needed by species.
+### This file contains a set of life-history related utility functions needed by species.
 ###
 
-#TODO initrandompopulation
+"""
+    initrandompopulation(popsize, asexual=true)
 
-#TODO killanimal!
+A simplified version of `initpopulation()`. Creates a function that initialises
+`popsize` individuals, spread at random across the landscape. If `popsize` is less
+than 1, it is interpreted as a population density (i.e. 1 animal per `popsize` pixels).
+"""
+function initrandompopulation(popsize::Float64, asexual::Bool=true)
+    initfunc = function(species::Dict{String,Any}, model::AgentBasedModel)
+        if popsize < 1.0
+            x, y = size(model.landscape)
+            popsize = Int(round(x*y*popsize))
+        end
+        for i in 1:popsize
+            sex = asexual ? hermaphrodite : rand([male, female])
+            add_agent!(Animal, model, species, sex, 0)
+        end
+        @debug "Initialised $(popsize) $(species["name"])s."
+    end
+    return initfunc
+end
 
-#TODO reproduce!
+#TODO initpopulation
 
-#TODO parentsalive?
+"""
+    reproduce!(animal, model, n=1)
+
+Produce one or more offspring for the given animal at its current location.
+"""
+function reproduce!(animal::Animal, model::AgentBasedModel, n::Int64=1)
+    # XXX at the moment we don't have intra-specific variation, so currently we
+    # don't need sexual recombination here
+    for i in 1:n
+        sex = animal.sex == hermaphrodite ? hermaphrodite : rand([male, female])
+        add_agent!(animal.pos, Animal, model, animal.traits, sex, 0)
+    end
+    @debug "$(animal.traits["name"]) $(animal.id) has reproduced."
+end
+
+"""
+    kill(animal, model, probability=1.0, cause="")
+
+Kill this animal, optionally with a given percentage probability.
+Returns true if the animal dies, false if not.
+"""
+function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0, cause::String="")
+    if rand() < probability
+        postfix = isempty(cause) ? "." : " from $cause."
+        @debug "$(animal.traits["name"]) $(animal.id) has died$(postfix)"
+        kill_agent!(animal, model)
+        return true
+    end
+    return false
+end
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 70c13b3f77827918fe2ba521b432760645c3d695..71dad4008376f919443ea08195a26725fae6a019 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -6,6 +6,9 @@
 ## An enum used to assign a sex to each animal
 @enum Sex hermaphrodite male female
 
+## This will be prepended to the names of the functions created by @species
+const SPECIESPREFIX = "speciesfunction_"
+
 """
     Animal
 
@@ -19,6 +22,7 @@ by trait dictionaries passed by them during initialisation.
     traits::Dict{String,Any}
     sex::Sex
     age::Int32
+    #XXX keep track of parents and/or offspring?
 end
 
 
@@ -33,7 +37,7 @@ custom syntax to describe species' biology:
 
 ```julia
 @species name begin
-    initialise! = initrandompopulation()
+    initialise! = initpopulation()
     phase = "phase1"
     ...
     @phase phase1 begin
@@ -51,14 +55,18 @@ life cycle (see the documentation to `@phase` for details).
 There are two parameters that all species must define:
 - `initialise!` should specify a function that will be used to create
     the starting population for this species. This function must take
-    exactly one argument (an `AgentBasedModel` object).
+    two arguments, a species dict and an `AgentBasedModel` object.
+    The easiest way to create this function is by using `initpopultion()`.
 - `phase` should be a string specifying the name of the first phase
     that individuals of this species will be assigned to on birth.
+
+Access to the rest of the model is given by the `model` variable (an object
+of type `AgentBasedModel`).
 """
 macro species(name, body)
-    funcname = "speciesfunction_"*name
+    funcname = Symbol(SPECIESPREFIX*string(name))
     quote
-        Core.@__doc__ function $(esc(funcname))()
+        Core.@__doc__ function $(esc(funcname))(model::AgentBasedModel)
             $(esc(:name)) = string($(QuoteNode(name)))
             $(esc(body))
             vardict = Base.@locals
@@ -91,13 +99,14 @@ variables:
     `model.landscape` (a two-dimensional array of pixels containing geographic
     information).
 
-The utility macros `@respond`, `@trait`, and `@here` can be used within the body of
-`@phase` as short-hand for common expressions.
+Several utility macros can be used within the body of `@phase` as a short-hand for
+common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`
 
 To transition an individual to another phase, simply redefine its phase variable:
 `@trait(phase) = "newphase"`.
 """
 macro phase(name, body)
+    #XXX Using an undeclared variable in the body doesn't throw an error?
     :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end)
 end
 
@@ -122,7 +131,11 @@ A utility macro to quickly access an animal's trait value.
 This can only be used nested within `@phase`.
 """
 macro trait(traitname)
-    :(animal.traits[string($(QuoteNode(name)))])
+    if traitname in fieldnames(Animal)
+        :(animal.$(traitname))
+    else
+        :(animal.traits[string($(QuoteNode(traitname)))])
+    end
 end
 
 """
@@ -135,6 +148,26 @@ macro here(property)
     :(model.landscape[animal.pos...].$(property))
 end
 
+"""
+    @kill
+
+Kill this animal. This is a thin wrapper around `kill!()`, and passes on any arguments.
+This can only be used nested within `@phase`.
+"""
+macro kill(args...)
+    :(kill!(animal, model, $(args...)))
+end
+
+"""
+    @reproduce
+
+Let this animal reproduce. This is a thin wrapper around `reproduce!()`, and passes on
+any arguments. This can only be used nested within `@phase`.
+"""
+macro reproduce(args...)
+    :(reproduce!(animal, model, $(args...)))
+end
+
 
 ### FUNCTIONS INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
 
@@ -156,9 +189,9 @@ 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")
-        funcname = Symbol("speciesfunction_"*s)
-        species = @eval $funcname()
-        species["initialise!"](model)
+        funcname = Symbol(SPECIESPREFIX*s)
+        species = @eval $funcname($model)
+        species["initialise!"](species, model)
     end
     # Initialise the data output
     initecologicaldata()
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index 53a045382a40e1529bc0de5d3cd4362d08b74d49..4843816234b5afa6f9720df61a2a98d6303b7bea 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -8,18 +8,21 @@
 
 @species Skylark begin
 
-    initialise! = initrandompopulation()
-    phase = "egg"
-
+    popdensity = 1/10000
     lifeexpectancy = 365*5
+
     eggharvestmortality = 0.9
+    eggpredationmortality = 0.1
+    
+    initialise! = initrandompopulation(popsize)
+    phase = "egg"
     
     @phase egg begin
-        if !parentsalive(animal)
-            killanimal(animal)
-        end
+        @kill(@trait(eggpredationmortality), "predation")
 
-        @respond harvest killanimal(animal, eggharvestmortality)
+        #TODO dies if parents die
+        
+        @respond harvest @kill(@trait(eggharvestmortality), "harvest")
 
         if animal.age == 14
             @trait(phase) = "nestling"