Files
passepartout/org/symbolic-identity.org
Amr Gharbeia b9a4318ef8 reorg: tangle to XDG, remove stale lisp files, fix tui input
- Changed all 50 org file :tangle targets from ../lisp/ to
  ~/.local/share/passepartout/lisp/ (XDG data dir)
- Removed 49 generated .lisp files from project lisp/ directory
- Removed tests/system-integration-tests.lisp (generated)
- Removed lisp/*.fasl (compiled, stale)
- Updated core-manifest.org to tangle .asd to XDG root
- Remapped quicklisp symlink: local-projects/passepartout → XDG

TUI fixes in channel-tui-main.org:
- Removed with-raw-terminal (stty raw breaks fd 0 reads in this SBCL)
- Use cat subprocess + pipe for keyboard input (via :input :interactive)
- Blocking read-char on pipe with with-timeout 0.1s for daemon processing
- Key events queued via drain-queue alongside daemon messages
- Full dialog key routing (Escape, Up/Down, Enter, filters, Backspace)
- SIGWINCH resize handling
- Post-handshake backend-size re-query
- Daemon version in status bar (was v0.5.0 hardcoded)
- Handshake version stored in state, no add-msg
- :daemon-version and :size-queried in state plist
- view-status uses draw-rect for background
- Test section gated with #+passepartout-tests
2026-05-14 12:34:06 -04:00

4.3 KiB

Symbolic Identity — Agent Self-Concept

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).
(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)

Test Squad

Test Package

(defpackage :passepartout-identity-tests
  (:use :common-lisp :fiveam :passepartout)
  (:export :identity-suite))

Test Suite

(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))))