diff --git a/Config.toml b/Config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..c83f24dad1a0dad3919ac3316ecc6ff37366303b
--- /dev/null
+++ b/Config.toml
@@ -0,0 +1,5 @@
+[per-session]
+maxfiles = 20
+
+[muscle-activity]
+maxlength = 1001
diff --git a/Project.toml b/Project.toml
index 3c346dd2cdfd26f11f88e55615bde911084c7466..92409a387837513b5781a01d55322e3bee3853d1 100644
--- a/Project.toml
+++ b/Project.toml
@@ -21,3 +21,4 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 Stipple = "4acbeb90-81a0-11ea-1966-bdaff8155998"
 StippleUI = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3"
 StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
+TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
diff --git a/front/Manifest.toml b/front/Manifest.toml
index 38f7e3b2bcde9368c0975ebfd0d93075e81c3822..3de4c3baa3ba5bb3930c2adde55d3ba205fee0cf 100644
--- a/front/Manifest.toml
+++ b/front/Manifest.toml
@@ -1,8 +1,8 @@
 # This file is machine-generated - editing it directly is not advised
 
-julia_version = "1.10.4"
+julia_version = "1.10.5"
 manifest_format = "2.0"
-project_hash = "b5504022439d7b9ed52b08f10117d0c48e04d5a8"
+project_hash = "7b4a57cbc9fc956109742c120d30a1b52ed917fd"
 
 [[deps.ArgParse]]
 deps = ["Logging", "TextWrap"]
@@ -726,7 +726,7 @@ version = "1.2.13+1"
 [[deps.libblastrampoline_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
-version = "5.8.0+1"
+version = "5.11.0+0"
 
 [[deps.nghttp2_jll]]
 deps = ["Artifacts", "Libdl"]
diff --git a/src/MuscleActivities.jl b/src/MuscleActivities.jl
index 6d09d77fcd829c5020647875a600506c222828af..86b74a322943c47270c03d17afa896e522d4ce12 100644
--- a/src/MuscleActivities.jl
+++ b/src/MuscleActivities.jl
@@ -6,6 +6,7 @@ using Random
 using JSON3
 using StructTypes
 using OrderedCollections: OrderedDict
+using ..Storage
 
 export MuscleActivity, HalfSegmentActivity, resample!
 
@@ -194,6 +195,14 @@ function contiguous(signal, i)
 end
 
 function to_json_file(filepath::String, object)
+    session_bucket = getbucket(child_resource=filepath)
+    @assert isdir(session_bucket)
+    records = length(readdir(session_bucket))
+    maxfiles = getconfig("per-session", "maxfiles")
+    if !(isnothing(maxfiles) || records <= maxfiles)
+        clear_oldest(session_bucket)
+    end
+    #
     open(filepath, "w") do f
         write_json(f, object)
     end
diff --git a/src/NyxUI.jl b/src/NyxUI.jl
index 47e418b11690c8e8c37c31ff35810ec988cd84a1..ed8342b1f50bf4e256eead96c821cfe10936f972 100644
--- a/src/NyxUI.jl
+++ b/src/NyxUI.jl
@@ -1,9 +1,11 @@
 module NyxUI
 
+include("Storage.jl")
 include("MuscleActivities.jl")
 include("BonitoServer.jl")
 include("GenieExtras.jl")
 
+using .Storage
 using .MuscleActivities
 using .BonitoServer
 using .GenieExtras
diff --git a/src/Storage.jl b/src/Storage.jl
new file mode 100644
index 0000000000000000000000000000000000000000..24c879e4f79c269a1af82ceacf2e7e70b57f35aa
--- /dev/null
+++ b/src/Storage.jl
@@ -0,0 +1,64 @@
+module Storage
+
+using Dates
+using TOML
+
+export getconfig, getbucket, clear_oldest
+
+const __storage_location__ = get(ENV, "STORAGE",
+                                 joinpath(dirname(Base.current_project()), "storage"))
+
+const __config_file__ = get(ENV, "CONFIGFILE",
+                            joinpath(dirname(Base.current_project()), "Config.toml"))
+
+const __config__ = Ref{Union{Nothing, Dict{String, Any}}}(nothing)
+
+function getconfig()
+    if isnothing(__config__[])
+        __config__[] = TOML.parsefile(__config_file__)
+    end
+    return __config__[]
+end
+
+function getconfig(key, morekeys...; default=nothing)
+    config = getconfig()
+    i = 0
+    while key in keys(config)
+        config = config[key]
+        i += 1
+        i <= length(morekeys) || break
+        key = morekeys[i]
+    end
+    config isa Dict ? default : config
+end
+
+getbucket(session_id) = joinpath(__storage_location__, "exports", session_id)
+
+function getbucket(session_id, mode)
+    session_bucket = getbucket(session_id)
+    if mode === :read
+        session_bucket
+    elseif mode === :write
+        date_time = Dates.format(now(), "yyyymmdd_HHMMSS")
+        newdir = joinpath(session_bucket, date_time)
+        mkpath(newdir)
+        newdir
+    else
+        throw("`mode` not in [:read, :write]: mode=$mode")
+    end
+end
+
+function getbucket(; child_resource)
+    parts = splitpath(child_resource)
+    session_id = parts[findfirst(==("exports"), parts) + 1]
+    return getbucket(session_id)
+end
+
+function clear_oldest(session_bucket)
+    oldest = first(sort(readdir(session_bucket; join=false)))
+    oldest = joinpath(session_bucket, oldest)
+    @info "Deleting directory" dir=oldest
+    rm(oldest; recursive=true)
+end
+
+end
diff --git a/src/apps/muscles/Backbone.jl b/src/apps/muscles/Backbone.jl
index 85940068810662c2323373bad17318f678f4dfba..21868e65dd545970b26baa197231faf9a1808589 100644
--- a/src/apps/muscles/Backbone.jl
+++ b/src/apps/muscles/Backbone.jl
@@ -2,8 +2,8 @@ module Backbone
 
 using NyxWidgets.Base: Cache
 using NyxUI.MuscleActivities
+using NyxUI.Storage
 using Bonito
-using Dates
 
 include("MuscleWidgets.jl")
 using .MuscleWidgets
@@ -132,15 +132,10 @@ end
 
 ## persistent storage
 
-const __storage_location__ = get(ENV, "STORAGE",
-                                 joinpath(dirname(Base.current_project()), "storage"))
-
 function exportsequence(session_id)
     sequence = getsequence(session_id)
     isempty(sequence.program_name) && return ""
-    date_time = Dates.format(now(), "yyyymmdd_HHMMSS")
-    dir = joinpath(__storage_location__, "exports", session_id, date_time)
-    mkpath(dir)
+    dir = getbucket(session_id, :write)
     filepath = joinpath(dir, sequence.program_name * ".json")
     MuscleActivities.to_json_file(filepath, sequence)
     return filepath
diff --git a/src/apps/muscles/app.jl b/src/apps/muscles/app.jl
index 4a70c66b29d94adcab2afefc91d8c10a567e8e58..619b12b911ae9b4b8d7e7b64850f926054e35ff4 100644
--- a/src/apps/muscles/app.jl
+++ b/src/apps/muscles/app.jl
@@ -1,6 +1,6 @@
 module MuscleApp
 
-using NyxUI
+using NyxUI, NyxUI.Storage
 using Genie, GenieSession, Stipple, StippleUI
 import Stipple: @app, @init, @private, @in, @out, @onchange, @onbutton, @notify
 
@@ -13,6 +13,8 @@ const default_colormap = Backbone.MuscleWidgets.__default_colormap__
 
 const colormaps = Backbone.MuscleWidgets.__colormaps__
 
+const maxlength = getconfig("muscle-activity", "maxlength")
+
 const bonito_app = NamedApp(:inherit, Backbone.app)
 
 @app begin
@@ -88,6 +90,10 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
             @notify "time series length must be integer" :info
             series_length = round(series_length)
             valid = false
+        elseif !isnothing(maxlength) && maxlength < series_length
+            @notify "time series is too long" :info
+            series_length = length(seq.times)
+            valid = false
         end
         #
         if valid && !(start_time == seq.times[1] && time_interval == step(seq.times) &&