Skip to content

Living Course settings

Living Course keeps a course built with the Course Builder in sync with its sources. Authors work with it in the studio (author guide); this page is for the person who runs the installation. It adds no admin route: everything is configured by environment variables, one permission and a few commands.

The module ships with the API and is always loaded. After an upgrade, run once per tenant:

Terminal window
php artisan migrate --force
php artisan db:seed --class="Ulams\\LivingCourse\\Database\\Seeders\\LivingCoursePermissionSeeder" --force
php artisan living-course:backfill # first revision for courses built before the upgrade
php artisan gift:snapshot-max-scores # keeps old quiz results comparable (progress rules)

Add --domain=<tenant> on a multi-tenant installation. The seeder creates the permission living_course_review and gives it to the admin role. The author of a session and admins can always act; give living_course_review to tutors who should review updates of courses they did not build.

The requirements of the Course Builder apply: a configured AI_DRIVER, a queue worker for the builder queue (COURSE_BUILDER_QUEUE, default builder, or LIVING_COURSE_QUEUE; see the Course Builder queue notes: retry_after must stay above the 1800 s job timeout) and the scheduler, which runs living-course:poll every 15 minutes.

Variable Default Meaning
LIVING_COURSE_CONNECTORS upload,git,url Connectors offered to authors. Plugins can register more.
LIVING_COURSE_QUEUE_CONNECTION, LIVING_COURSE_QUEUE the Course Builder queue (<driver>-builder, queue builder) Where source checks and analysis steps run. Use a connection whose retry_after is above 1800 s.
LIVING_COURSE_MIN_POLL_MINUTES 60 Shortest polling interval an author can choose.
LIVING_COURSE_WEBHOOK_DEBOUNCE_SECONDS 600 Pushes within this window collapse into one check.
LIVING_COURSE_AUTO_ANALYSE_USD 0.50 Estimated cost below which analysis starts by itself; above it the author confirms.
LIVING_COURSE_PROPOSAL_COST_USD 2 Hard cap of one update proposal.
LIVING_COURSE_SOURCE_MONTHLY_USD 10 Hard cap per source and month.
LIVING_COURSE_MAX_GROUPS 40 Most analysis groups of one proposal.
LIVING_COURSE_MAX_FRAGMENTS 4000 Most fragments per revision before a check fails with a readable message.
LIVING_COURSE_ALLOWED_HOSTS empty Allow-list of hosts (comma separated) the connectors may reach. Empty means any public host; once set, only these.
LIVING_COURSE_INSECURE_HOSTS empty Hosts allowed over plain HTTP or on private addresses (development only).
LIVING_COURSE_LEARNER_NOTICE_AFTER_DAYS 3 Days before an unseen update notice reaches a learner by e-mail.
AI_TASK_UPDATE_PROFILE the default profile Model profile of the analysis (update task); the model name lives in config/ai.php.

Not configurable by environment variable (set in api/packages/living-course/config/living_course.php): at most 48 checks per connection and day, 5 update proposals per source and day, 3 regenerations per item, 3000 output tokens per analysis group, 3 failed checks before a connection shows Error, and a weekly digest of learner notices.

Every model call of the analysis is logged in ai_calls with its model, tokens and cost, and the proposal as subject, so the cost log shows it.

  • Upload needs nothing. Git reads GitHub, GitLab and Gitea or Forgejo hosts through their APIs with a token the author provides; tokens are stored encrypted and never returned. URL reads up to 20 web pages and stores their Markdown.
  • Every outbound request goes through one SSRF-safe client: private and link-local addresses, redirects to them and unlisted hosts are refused.
  • The webhook of a connection is https://<tenant API host>/api/living-course/webhooks/{id}; the studio shows it with its secret. GitHub (X-Hub-Signature-256), GitLab (X-Gitlab-Token) and Gitea signatures are verified, deliveries are deduplicated and the endpoint is throttled. Polling is the fallback when a webhook is not possible. Signature headers, replies and a curl example are in Living Course internals. The route must be reachable from your Git host without a login; it is throttled to 60 requests a minute per webhook, refuses bodies over 1 MB and logs a refused signature once a minute per connection in the audit trail (webhook.rejected).

The scheduler (php artisan schedule:run every minute) runs living-course:poll every 15 minutes. It queues a check for every Git or web connection whose time has come: schedules are hourly, daily and weekly (manual and uploads are never polled), never shorter than LIVING_COURSE_MIN_POLL_MINUTES, each with a random delay of up to 10 % so tenants do not all fetch at once. After three failed checks a connection shows Error with the last message and backs off to daily; Check now, resuming or fixing the settings clears it. Checks run as jobs on the builder queue.

Analysis is estimated before it starts. At or below LIVING_COURSE_AUTO_ANALYSE_USD it runs by itself (if the connection’s automatic analysis is on); above it the proposal waits as awaiting_analysis until an author confirms (ulams living proposals analyse --confirm-estimate). If the estimate would exceed LIVING_COURSE_PROPOSAL_COST_USD, or this source’s month (LIVING_COURSE_SOURCE_MONTHLY_USD), the proposal becomes budget_blocked with the reason and the changed elements are listed for editing by hand. The course session budget and AI_LIMIT_TENANT_MONTHLY_USD of the Course Builder also apply to every call. Change detection, staleness and the audit trail cost nothing and keep working.

Command Use
living-course:poll Checks the sources that are due; scheduled every 15 minutes, also prunes webhook deliveries older than 30 days.
living-course:backfill Creates the first revision of sessions that have none (--session=<id> for one). Safe to repeat.
living-course:eval Runs the update fixtures end to end (--fixtures=all or coffee,git,injection) and writes a report; --live calls the real model, capped by --max-usd (default 1), --record stores the answers for CI replay, --author=<user id>.
gift:snapshot-max-scores Stores the maximum score on existing quiz results.

The audit trail is append-only (a database trigger refuses updates and deletes) and hash-chained. Admins can export it as CSV or JSON and verify the chain from the studio or with GET /api/admin/living-course/audit/verify.