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.
Turn it on
Section titled “Turn it on”The module ships with the API and is always loaded. After an upgrade, run once per tenant:
php artisan migrate --forcephp artisan db:seed --class="Ulams\\LivingCourse\\Database\\Seeders\\LivingCoursePermissionSeeder" --forcephp artisan living-course:backfill # first revision for courses built before the upgradephp 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.
Environment variables
Section titled “Environment variables”| 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.
Connectors and webhooks
Section titled “Connectors and webhooks”- 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 acurlexample 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).
Scheduler and checks
Section titled “Scheduler and checks”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.
When a cost limit is reached
Section titled “When a cost limit is reached”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.
Commands
Section titled “Commands”| 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.