Skip to content

Commit e24d76a

Browse files
committed
Load user Lisp configuration
1 parent c98a72f commit e24d76a

14 files changed

Lines changed: 170 additions & 13 deletions

autolith.asd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
(:file "overlays")
4848
(:file "durable-mutations")
4949
(:file "image-commits")
50+
(:file "user-init")
5051
(:file "generations")
5152
(:file "self-status")
5253
(:file "self-discard")
@@ -111,6 +112,7 @@
111112
(:file "release-script-tests")
112113
(:file "release-server-tests")
113114
(:file "application-tests")
115+
(:file "user-init-tests")
114116
(:file "tests"))))
115117
:perform (asdf:test-op (operation component)
116118
(declare (ignore operation component))

docs/autolith-minimal-technical-spec.org

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ The active SBCL image contains:
7676
- Self-inspection and self-modification facilities.
7777
- Loaded Lisp dependencies.
7878

79+
After the tracked system and selected private image commit load, Autolith loads
80+
=${XDG_CONFIG_HOME:-~/.config}/autolith/init.lisp= when it exists. This
81+
user-authored Common Lisp is the final executable configuration layer and
82+
therefore takes precedence over built-in and privately committed definitions.
83+
It executes with the user's full privileges, is loaded again when reconnecting
84+
a retained generation, and is never included in the pristine recovery image.
85+
7986
This process is expected to evolve while it is running.
8087

8188
Setup may install a preloaded baseline active image containing compiled Autolith
@@ -402,7 +409,7 @@ with a distinguished recovery status.
402409

403410
* 8. Persistence
404411

405-
Autolith persists eight kinds of state:
412+
Autolith persists nine kinds of state:
406413

407414
- Source in Git.
408415
- Conversations as S-expression files.
@@ -412,6 +419,7 @@ Autolith persists eight kinds of state:
412419
- A private Git history retaining complete image-commit artifacts.
413420
- Working generations as saved Lisp cores and reconstruction scripts.
414421
- Worker images as immutable SBCL cores with manifests and durable notes.
422+
- User-authored executable configuration under the XDG config root.
415423

416424
** 8.1 Conversation Files
417425

@@ -493,7 +501,20 @@ Every executed tool result records nonnegative CPU and real durations. Terminal
493501
presentation shows both durations. Calls rejected before execution and repaired
494502
unknown-outcome records omit timing rather than inventing a duration.
495503

496-
** 8.2 Mutation Journal
504+
** 8.2 User Configuration
505+
506+
The optional =${XDG_CONFIG_HOME:-~/.config}/autolith/init.lisp= file is the
507+
explicit entry point for user Lisp. Autolith loads it in the =AUTOLITH= package
508+
after the tracked system and selected private image commit, before the first
509+
provider request. A retained generation reconnect loads the current file again
510+
instead of relying on a copy captured in its core.
511+
512+
The file is trusted executable code with the same user privileges as Autolith.
513+
A load failure is a structured startup error identifying the file and original
514+
condition. The file is separate from legacy mutation overlays and private image
515+
commits, so mutation rollback never discards user policy.
516+
517+
** 8.3 Mutation Journal
497518

498519
The mutation journal is append-only and records enough information to
499520
understand recent changes after a crash.
@@ -512,7 +533,7 @@ A journal entry should identify:
512533
The journal is for recovery and diagnosis. It does not replace private
513534
image commits or their complete replay scripts.
514535

515-
** 8.3 Persistent Memories
536+
** 8.4 Persistent Memories
516537

517538
Persistent memories are stored in one readable =memories.sexp= file beneath the
518539
data root. The file begins with a versioned header and continues as an
@@ -540,7 +561,7 @@ a short excerpt for selection; exact content remains available through
540561
=memory.search= and =memory.read=. Memory corruption degrades this catalog to a
541562
bounded unavailable notice instead of preventing unrelated model work.
542563

543-
** 8.4 Workspace Agendas
564+
** 8.5 Workspace Agendas
544565

545566
Workspace agendas are stored in one readable =agendas.sexp= file beneath the
546567
data root. The complete versioned state is published atomically with private

docs/guide.org

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,19 @@ failures. =/later= lists the queue and =/later cancel ID= removes one entry.
303303

304304
Use a double slash to send a user message that begins with a literal slash.
305305

306+
* User Lisp configuration
307+
308+
Autolith loads the optional
309+
=${XDG_CONFIG_HOME:-~/.config}/autolith/init.lisp= after its tracked system and
310+
selected private image commit, before it creates the provider request path. The
311+
file is read in the =AUTOLITH= package and is loaded again when reconnecting a
312+
retained generation, so it remains the final user-controlled configuration
313+
layer. It is ordinary Common Lisp running with the user's full privileges.
314+
315+
This entry point is distinct from =overlays/*.lisp=, which exist only to migrate
316+
old self-modifications. User configuration is never built into the pristine
317+
recovery image.
318+
306319
Conversations are append-only readable forms under
307320
=${XDG_DATA_HOME:-~/.local/share}/autolith/conversations/=. A truncated final form is
308321
ignored after a crash; malformed complete records are rejected. If a crash

src/application.lisp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@
206206
(configuration-ensure-directories preferred-configuration)
207207
(durable-mutations-load preferred-configuration)
208208
(let* ((overlay-failures (image-state-load preferred-configuration))
209+
(user-init-pathname (user-init-load preferred-configuration))
209210
(reasoning-traces-p
210211
(preferences-reasoning-traces-p preferred-configuration))
211212
(permission-state (permissions-load preferred-configuration))
@@ -240,6 +241,7 @@
240241
:ui ui
241242
:permission-state permission-state
242243
:reasoning-traces-p reasoning-traces-p)))
244+
(declare (ignore user-init-pathname))
243245
(setf (application-overlay-failures application) overlay-failures)
244246
(application--load-goal application)
245247
application)))
@@ -265,29 +267,34 @@
265267
:model (configuration-model previous)
266268
:reasoning-effort (configuration-reasoning-effort previous)
267269
:immutable-p effective-immutable-p))
270+
(prepared-configuration
271+
(progn
272+
(configuration-ensure-directories retained-configuration)
273+
(user-init-load retained-configuration)
274+
retained-configuration))
268275
(reasoning-traces-p
269-
(preferences-reasoning-traces-p retained-configuration))
270-
(permission-state (permissions-load retained-configuration))
276+
(preferences-reasoning-traces-p prepared-configuration))
277+
(permission-state (permissions-load prepared-configuration))
271278
(recovery-conversation-id
272279
(let ((value (uiop:getenv "AUTOLITH_RECOVERY_CONVERSATION_ID")))
273280
(and (non-empty-string-p value) value)))
274281
(overlay-failures nil)
275282
(conversation
276283
(cond
277284
(conversation-id
278-
(conversation-load-by-id retained-configuration conversation-id))
285+
(conversation-load-by-id prepared-configuration conversation-id))
279286
(recovery-conversation-id
280-
(conversation-load-by-id retained-configuration
287+
(conversation-load-by-id prepared-configuration
281288
recovery-conversation-id))
282289
((conversation-persisted-p retained-conversation)
283290
(conversation-load-by-id
284-
retained-configuration
291+
prepared-configuration
285292
(conversation-identifier retained-conversation)))
286293
(t
287-
(conversation-create retained-configuration))))
294+
(conversation-create prepared-configuration))))
288295
(configuration
289296
(application--configuration-for-conversation
290-
retained-configuration
297+
prepared-configuration
291298
conversation))
292299
(provider (provider-create
293300
configuration

src/conditions.lisp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@
1616
()
1717
(:documentation "A failure caused by invalid or unavailable configuration."))
1818

19+
(define-condition user-init-error (configuration-error)
20+
((pathname
21+
:initarg :pathname
22+
:reader user-init-error-pathname
23+
:type pathname
24+
:documentation "The user initialization file that could not be loaded.")
25+
(cause
26+
:initarg :cause
27+
:reader user-init-error-cause
28+
:type serious-condition
29+
:documentation "The underlying condition signaled while loading the file."))
30+
(:documentation "Loading the user's executable Autolith configuration failed."))
31+
1932
(define-condition working-directory-error (configuration-error)
2033
((requested-path
2134
:initarg :requested-path

src/configuration.lisp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@
8383
:reader configuration-cache-root
8484
:type pathname
8585
:documentation "The root for replaceable caches and temporary artifacts.")
86+
(config-root
87+
:initarg :config-root
88+
:reader configuration-config-root
89+
:type pathname
90+
:documentation "The root for user-authored executable configuration.")
8691
(codex-auth-path
8792
:initarg :codex-auth-path
8893
:reader configuration-codex-auth-path
@@ -189,6 +194,9 @@
189194
(cache-home (environment-directory
190195
"XDG_CACHE_HOME"
191196
(merge-pathnames ".cache/" home)))
197+
(config-home (environment-directory
198+
"XDG_CONFIG_HOME"
199+
(merge-pathnames ".config/" home)))
192200
(codex-home (environment-directory
193201
"CODEX_HOME"
194202
(merge-pathnames ".codex/" home)))
@@ -220,6 +228,7 @@
220228
:data-root (merge-pathnames "autolith/" data-home)
221229
:state-root (merge-pathnames "autolith/" state-home)
222230
:cache-root (merge-pathnames "autolith/" cache-home)
231+
:config-root (merge-pathnames "autolith/" config-home)
223232
:codex-auth-path (merge-pathnames "auth.json" codex-home)
224233
:model selected-model
225234
:reasoning-effort selected-effort
@@ -253,6 +262,7 @@ Selecting a different model recomputes the context window for that model."
253262
:data-root (configuration-data-root configuration)
254263
:state-root (configuration-state-root configuration)
255264
:cache-root (configuration-cache-root configuration)
265+
:config-root (configuration-config-root configuration)
256266
:codex-auth-path (configuration-codex-auth-path configuration)
257267
:model (or model (configuration-model configuration))
258268
:reasoning-effort (or reasoning-effort
@@ -352,10 +362,11 @@ Selecting a different model recomputes the context window for that model."
352362

353363
(-> configuration-ensure-directories (configuration) configuration)
354364
(defun configuration-ensure-directories (configuration)
355-
"Create CONFIGURATION's private data, state, and cache directories."
365+
"Create CONFIGURATION's private data, state, cache, and config directories."
356366
(dolist (directory (list (configuration-data-root configuration)
357367
(configuration-state-root configuration)
358-
(configuration-cache-root configuration)))
368+
(configuration-cache-root configuration)
369+
(configuration-config-root configuration)))
359370
(ensure-directories-exist directory))
360371
configuration)
361372

@@ -364,6 +375,11 @@ Selecting a different model recomputes the context window for that model."
364375
"Return the directory containing append-only conversation files."
365376
(merge-pathnames "conversations/" (configuration-data-root configuration)))
366377

378+
(-> configuration-user-init-path (configuration) pathname)
379+
(defun configuration-user-init-path (configuration)
380+
"Return the user-authored Lisp initialization pathname."
381+
(merge-pathnames "init.lisp" (configuration-config-root configuration)))
382+
367383
(-> configuration-memory-path (configuration) pathname)
368384
(defun configuration-memory-path (configuration)
369385
"Return the append-only persistent memory pathname."

src/user-init.lisp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
(in-package #:autolith)
2+
3+
;;;; -- User Initialization --
4+
5+
(defvar *user-init-loading-p* nil
6+
"True only while Autolith loads the user's executable configuration.")
7+
8+
(-> user-init-load (configuration) (option pathname))
9+
(defun user-init-load (configuration)
10+
"Load CONFIGURATION's user initialization file and return its pathname.
11+
12+
The file is read in the AUTOLITH package after tracked and privately committed
13+
definitions have loaded. It executes with the user's full privileges."
14+
(let ((pathname (configuration-user-init-path configuration)))
15+
(when (uiop:file-exists-p pathname)
16+
(handler-case
17+
(let ((*package* (find-package '#:autolith))
18+
(*user-init-loading-p* t))
19+
(load pathname :verbose nil :print nil)
20+
pathname)
21+
(serious-condition (cause)
22+
(error 'user-init-error
23+
:message (format nil "Could not load user initialization at ~A: ~A"
24+
pathname cause)
25+
:pathname pathname
26+
:cause cause))))))

tests/application-tests.lisp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,7 @@
15661566
:data-root (configuration-data-root base)
15671567
:state-root (configuration-state-root base)
15681568
:cache-root (configuration-cache-root base)
1569+
:config-root (configuration-config-root base)
15691570
:codex-auth-path (configuration-codex-auth-path base)
15701571
:model (configuration-model base)
15711572
:reasoning-effort (configuration-reasoning-effort base)

tests/memory-tests.lisp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:data-root (configuration-data-root configuration)
1414
:state-root (configuration-state-root configuration)
1515
:cache-root (configuration-cache-root configuration)
16+
:config-root (configuration-config-root configuration)
1617
:codex-auth-path (configuration-codex-auth-path configuration)
1718
:model (configuration-model configuration)
1819
:reasoning-effort

tests/search-tool-tests.lisp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
:data-root (configuration-data-root base)
1313
:state-root (configuration-state-root base)
1414
:cache-root (configuration-cache-root base)
15+
:config-root (configuration-config-root base)
1516
:codex-auth-path (configuration-codex-auth-path base)
1617
:model (configuration-model base)
1718
:reasoning-effort (configuration-reasoning-effort base)

0 commit comments

Comments
 (0)