All notable changes to this project are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
[v0.1.0] — 2026-09-03
First release. Resource-constrained scheduling on top of Tempo: Tempo answers when is this free?, Agenda answers what should I book, and where?
Added
The model.
Agenda.Resource,Place,Session,ArrangementandAllocation— people and rooms are the same kind of thing, differing only in their attributes.Requirements by description or by name.
needs/3matches on attributes —at_least/1,any_of/1,all_of/1;roster/3names exact resources, and a person's ownrequiresfolds into their room.Availability is derived, never stored.
open/2takes a Tempo value, an ISO 8601 string or anRRULE;free/2computes open minus busy on demand, honouringconcurrencyandbuffer_before/buffer_after.Planning one session.
Agenda.plan/3ranks the ways a session could be held — attribute eligibility, then availability, then co-availability across the roster, then slotting.Arranging a whole programme.
Agenda.arrange/3places every session with no resource in two places at once, no track clashing with itself, and consecutive sessions reachable across the place tree.Failures are sentences. Every refusal names the resource and each reason it failed;
Agenda.conflict/3returns the minimal set of sessions or demands in tension, via QuickXplain.When it will not all fit.
unplaced: :allowreturns{:partial, layout}with a reason per omission, and:pinnedfixes announced placements while searching around them.Preferences that cannot cost a placement.
Agenda.prefer/3scores layouts —:room_changes,:room_spread, or your own. Optimisation is two-pass, so the number placed is settled and proven first.Order, load and wishes.
Agenda.precede/4sequences sessions with an optional:gapand:within—within: "PT0S"is "immediately after";:limitscaps claims or hours per period and is honoured byplan/3as well asarrange/3, so booking one at a time cannot exceed a cap;:avoidsand:prefersmake a placement worse rather than impossible.Required and optional participants.
roster/3names resources that must be free;Agenda.invite/3names ones who need not be. Invitees never change whether a session can be held — only which time is best — and each arrangement records, inattending, which of them the chosen time suits.Demand, not only supply.
Agenda.interest/3records that one resource would like a session with another, andAgenda.meetings/3turns returned interest into sessions — one per mutually interested pair, each rostering both parties, so nobody being in two places at once is the constraint the library already enforces. Interest that is never returned is reported byAgenda.Interest.one_sided/1rather than quietly scheduled.The ledger.
allocate/2,release/2anddiff/3keep bookings true as sessions move,hold/3andconfirm/2take a resource tentatively, and:tagrecords what the time was for.Recurrence and reconciliation.
Agenda.every/3expands a session over an ISO 8601 recurrence orRRULE;Agenda.reconcile/3reports unaccounted and overclaimed time as interval sets, not quantities.Scale follows shape, not size. Candidate enumeration runs concurrently and sessions that cannot constrain each other are solved as separate problems: 1,200 sessions spread across twenty days lay out in about a second, while 500 competing for a single day exhaust the default
:nodesbudget — which reports itself rather than returning a partial answer.Optional integrations.
Agenda.from_ical/1reads open hours from an RFC 7953VAVAILABILITY— what a CalDAV server hands you when asked when someone is free.