diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl
index 35061b319ee172515545b80818f5f0b4e656a7a1..26eda952af2e65075ce77b1bcbf6262c4953fc7a 100644
--- a/src/crop/farmplot.jl
+++ b/src/crop/farmplot.jl
@@ -55,8 +55,19 @@ end
 
 ## UTILITY FUNCTIONS
 
+"""
+    isgrassland(farmplot, model)
+
+Classify a farmplot as grassland or not (i.e., is the landcover of >90% of its pixels grass?)
+"""
 function isgrassland(farmplot::FarmPlot, model::SimulationModel)
-    #TODO
+    proportiongrass = count(pos -> @landcover() == grass, farmplot.pixels) / length(farmplot.pixels)
+    if proportiongrass > 0.9
+        return true
+    elseif proportiongrass > 0.1
+        @warn "Unclear classification: farm plot $(farmplot.id) has $(proportiongrass*100)% grass."
+    end
+    return false
 end
 
 """
diff --git a/src/farm/farm.jl b/src/farm/farm.jl
index 29390ed34d87d4a3e326a5ca5d6286d5bbee8a46..108f2efeb8e31133b9cf47b8a171d4b48e5e4418 100644
--- a/src/farm/farm.jl
+++ b/src/farm/farm.jl
@@ -3,9 +3,6 @@
 ### This file is responsible for managing the farm module(s).
 ###    
 
-###XXXXXXX In future, I want to expand Persefone into a proper ABM with multiple farm actors.
-###XXXXXXX However, at the moment, all fields are controlled centrally -> see farm/farm.jl
-
 """
     Farmer
 
@@ -24,11 +21,17 @@ end
 Update a farmer by one day.
 """
 function stepagent!(farmer::Farmer, model::SimulationModel)
-    #TODO
-    # - check each field, whether it can be harvested
-    # - if so, harvest it and set its crop to "no growth"
-    # - [later: calculate income based on yield and annual price]
-    # - if a field has been harvested, check if the next crop can be sown
+    for f in farmer.fields
+        field = model.farmplots[f]
+        ctype = croptype(field)
+        if ctype.group != "semi-natural" && isharvestable(field) #TODO implement
+            harvest!(field, model)
+            #XXX later: calculate income based on yield and annual price
+            (ctype.group != "grass") && @sow("no growth")
+        elseif cropname(field) == "no growth"
+            #TODO if a field has been harvested, check if the next crop can be sown
+        end
+    end     
 end
 
 """
@@ -37,5 +40,59 @@ end
 Initialise the model with a set of farm agents.
 """
 function initfarms!(model::SimulationModel)
-    #TODO
+    #XXX initially, we only have one farmer controlling all fields in the region
+    farmer = Farmer(1, collect(1:length(model.farmplots)), 0)
+    model.farmers = [farmer]
+    setasides = findsetasides(farmer, model) #TODO implement
+    for field in model.farmplots
+        if isgrassland(field)
+            @sow("permanent grassland (seeded)")
+        elseif field.id in setasides
+            @sow("permanent set-aside")
+        else
+            @sow("no growth")
+        end
+    end
+end
+
+"""
+    findsetasides(farmer, model)
+
+Return a vector of field IDs that this farmer should keep fallow to satisfy the configured
+set-aside rules.
+"""
+function findsetasides(farmer::Farmer, model::SimulationModel)
+    @param(farm.setaside) == 0 && return []
+    croparea = reduce(f -> isgrassland(f) ? 0 : length(f.pixels), model.farmplots[farmer.fields]) *
+        @param(world.mapresolution)^2
+    setasidearea = 0m²
+    setasides = []
+    for f in farmer.fields #XXX should be sorted smallest-largest for highest efficiency
+        field = model.farmplots[f]
+        isgrassland(f) && continue
+        push!(setasides, f)
+        setasidearea += length(field.pixels)*@param(world.mapresolution)^2
+        if setasidearea >= croparea*@param(farm.setaside)
+            @debug "Farmer $(farmer.id) has set aside $(setasidearea |> ha)."
+            return setasides
+        end
+    end
+end
+
+"""
+    @sow(cropname)
+
+Sow the named crop on the current field. Requires the variables `field` and `model`.
+"""
+macro sow(cropname)
+    :(sow!($(esc(:field)), $(esc(:model)), $(esc(cropname))))
+end
+
+"""
+    @harvest()
+
+Harvest the current field. Requires the variables `field` and `model`.
+"""
+macro harvest()
+    :(harvest!($(esc(:field)), $(esc(:model))))
 end
diff --git a/src/parameters.toml b/src/parameters.toml
index ee391ab5ee1c8a41e0263595061da7c649384d6f..9a5920f8b3c84e0e4ae9c7d25e7aea14b18062f4 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -31,7 +31,8 @@ weatherfile = "weather.csv" # name of the weather data file in the map directory
 	
 [farm]
 farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented)
-
+setaside = 0.04 # proportion of farm area set aside as fallow
+	
 [nature]
 #targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate - example species
 targetspecies = ["Skylark"] # list of target species to simulate