#+TITLE: Symbolic Identity — Agent Self-Concept #+FILETAGS: :skill:identity: #+PROPERTY: header-args:lisp :tangle ../lisp/symbolic-identity.lisp * Overview Load `~/memex/IDENTITY.org` into the agent's self-concept at daemon startup. The identity text is injected into the system prompt's `IDENTITY` section, between assistant name and reflection feedback. The file is user-editable and survives restarts. If the file is missing or empty, identity is silently `""` (no-op). * Contract 1. `(load-identity-file &optional path)`: Reads IDENTITY.org from `path` (default `~/memex/IDENTITY.org`). Sets `*agent-identity*` to the file content string. Returns the content string, or NIL if file missing/unreadable. 2. `(agent-identity)`: Returns the cached identity string (`*agent-identity*`), or `""` if identity has not been loaded. 3. `*agent-identity*`: Special variable holding the loaded identity text (string). #+begin_src lisp (in-package :passepartout) (defvar *agent-identity* "" "Identity text loaded from ~/memex/IDENTITY.org at startup. This variable holds the contents of the user's identity file. Loaded by `load-identity-file` at daemon/skill initialization, called from `agent-identity` for system prompt injection. The file is user-editable and persists across restarts. If the file is missing or empty, this variable remains \"\".") (defun load-identity-file (&optional (path nil path-p)) "Load agent identity from an org file. Reads the identity text file and caches it in `*agent-identity*`. If PATH is not provided, defaults to `~/memex/IDENTITY.org`. Returns the file content string on success, or NIL if the file does not exist or cannot be read." (let* ((file-path (if path-p (uiop:ensure-pathname path :ensure-absolute t) (merge-pathnames "memex/IDENTITY.org" (user-homedir-pathname))))) (when (uiop:file-exists-p file-path) (handler-case (let ((content (uiop:read-file-string file-path))) (setf *agent-identity* content) content) (error () nil))))) (defun agent-identity () "Return the currently loaded agent identity string." (or *agent-identity* "")) ;; Auto-load identity at skill init (load-identity-file) #+end_src * Test Squad ** Test Package #+begin_src lisp (defpackage :passepartout-identity-tests (:use :common-lisp :fiveam :passepartout) (:export :identity-suite)) #+end_src ** Test Suite #+begin_src lisp (in-package :passepartout-identity-tests) (def-suite identity-suite :description "Agent identity loading and caching") (in-suite identity-suite) (test test-load-identity-file-returns-content "Contract 1: load-identity-file reads an existing file, returns content." (let* ((path "/tmp/memex-test-identity.org") (content "### Personality - Friendly - Concise")) (with-open-file (f path :direction :output :if-exists :supersede) (write-string content f)) (unwind-protect (let ((result (passepartout::load-identity-file path))) (is (stringp result)) (is (search "Friendly" result)) (is (search "Concise" result))) (ignore-errors (delete-file path))))) (test test-load-identity-file-missing-nil "Contract 1: nil when file does not exist." (let ((result (passepartout::load-identity-file "/tmp/memex-nonexistent-xxxx.org"))) (is (null result)))) (test test-agent-identity-cached "Contract 2+3: agent-identity returns cached value after load." (let* ((path "/tmp/memex-test-identity2.org") (content "### Preferences - Use shell cautiously")) (with-open-file (f path :direction :output :if-exists :supersede) (write-string content f)) (unwind-protect (progn (passepartout::load-identity-file path) (let ((id (passepartout::agent-identity))) (is (search "shell cautiously" id)))) (ignore-errors (delete-file path))))) (test test-agent-identity-empty-default "Contract 2: returns empty string when nothing was loaded." (let ((prev passepartout::*agent-identity*)) (unwind-protect (progn (setf passepartout::*agent-identity* nil) (is (string= "" (passepartout::agent-identity)))) (setf passepartout::*agent-identity* prev)))) #+end_src