All notable changes to this project are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
[v0.3.0] — 2026-11-01
Breaking changes
Agenda.Allocation's:rolefield is:requirement, andAgenda.claim/4andAgenda.record/4take:requirementwhere they took:role: it names the requirement a resource fills, which iCalendar'sROLEdoes not.Agenda.Session.open_roles/1isopen_requirements/1.
Added
Agenda.free_busy/2gives each resource's claims as{:busy | :tentative, interval}pairs, iCalendar'sBUSYandBUSY-TENTATIVE, wherebusy/2gives the intervals alone.Agenda.cancel/2cancels a session, freeing what it held or booked: the session's name forrelease/2, completing hold, confirm and cancel.Agenda.Resource.cutype/1andresource_type/1read the conventionalcutype:andresource_type:attributes, iCalendar'sCUTYPEand RFC 9073'sRESOURCE-TYPE; a resource with nocutype:is:unknown.Agenda.Place.new/2takes:location_type, RFC 9073'sLOCATION-TYPE; theAgenda.Placemoduledoc maps a place ontoVLOCATIONand says what an export loses.An occurrence of
Agenda.Series.expand/3carries arecurrence_id, the start of its repetition (iCalendar'sRECURRENCE-ID), into its arrangements and allocations, beside theseriesname.
Changed
Agenda runs on Tempo 2.3 (
ex_tempo ~> 2.3).The
Agenda.Precedence,Agenda.Availability,Agenda.Session,Agenda.LedgerandAgenda.Trackmoduledocs record how each maps onto iCalendar, and what an export loses.
[v0.2.0] — 2026-10-28
Breaking changes
Agenda runs on Tempo 2.1. A date written as an interval's end is read through, as it is said:
"2026-06-15/2026-06-19"is Monday to Friday.A claim, a refinement or a session's window combined with hours in UTC or a zone is written on that time line; a floating one beside them is a
Tempo.FrameError. Awithin:window may still be floating.Agenda.Limit.bucket/2gives a week as{year, {:week, week}, nil}, the week as its calendar counts it, where it gave the date the week began on.
Changed
- A period in a reconciliation's sentences is written as Tempo writes it (
2026Y6M15D), and a span of a fiscal calendar as the Gregorian dates it falls on.
Fixed
A month in a reconciliation's breach sentence is named in full, where it was written
"2026-06-".A preference's context during
Agenda.arrange/3holds the pool the programme is arranged from, where it was always empty.
[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.