REFAC: Applied literate granularity and configuration externalization to loop
This commit is contained in:
@@ -23,6 +23,7 @@ ORG_AGENT_DAEMON_PORT=9105
|
||||
ORG_AGENT_WEB_PORT=8080
|
||||
DAEMON_HOST="0.0.0.0"
|
||||
HEARTBEAT_INTERVAL=60
|
||||
DAEMON_SLEEP_INTERVAL=3600
|
||||
|
||||
# Memex Integration
|
||||
# Inside Docker, /app/ is the root for consolidated notes
|
||||
|
||||
@@ -10,12 +10,21 @@ The Metabolic Loop is the high-level coordinator of the Org-Agent. It orchestrat
|
||||
2. **Reason:** Cognitive processing.
|
||||
3. **Act:** Physical side-effects.
|
||||
|
||||
** Package and Variables
|
||||
The loop requires thread-safe interrupt handling to ensure that the agent can be stopped gracefully without leaving the Lisp image in an inconsistent state.
|
||||
|
||||
#+begin_src lisp :tangle ../src/loop.lisp
|
||||
(in-package :org-agent)
|
||||
|
||||
(defvar *interrupt-flag* nil)
|
||||
(defvar *interrupt-lock* (bt:make-lock "harness-interrupt-lock"))
|
||||
(defvar *heartbeat-thread* nil)
|
||||
#+end_src
|
||||
|
||||
** The Metabolic Pipeline
|
||||
The `process-signal` function is the core metabolic processor. It iterates through the Perceive-Reason-Act gates until the signal is fully processed or an error state is reached. We have refined the error handling to ensure that memory rollbacks only occur on critical system failures, preventing transient tool errors from wiping short-term cognitive state.
|
||||
|
||||
#+begin_src lisp :tangle ../src/loop.lisp
|
||||
(defun process-signal (signal)
|
||||
"The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act."
|
||||
(let ((current-signal signal))
|
||||
@@ -32,32 +41,58 @@ The Metabolic Loop is the high-level coordinator of the Org-Agent. It orchestrat
|
||||
(setf current-signal (reason-gate current-signal))
|
||||
(setf current-signal (act-gate current-signal)))
|
||||
(error (c)
|
||||
(harness-log "METABOLISM CRASH: ~a - Initiating Micro-Rollback." c)
|
||||
(rollback-memory 0)
|
||||
(let ((sensor (ignore-errors (getf (getf current-signal :payload) :sensor))))
|
||||
(harness-log "METABOLISM CRASH [~a]: ~a" (or sensor :unknown) c)
|
||||
;; Only rollback on critical errors, not standard tool or loop errors
|
||||
(unless (member sensor '(:loop-error :tool-error :syntax-error))
|
||||
(harness-log "CRITICAL ERROR: Initiating Micro-Rollback.")
|
||||
(rollback-memory 0))
|
||||
(if (or (> depth 2) (member sensor '(:loop-error :tool-error)))
|
||||
(setf current-signal nil)
|
||||
(setf current-signal (list :type :EVENT :depth (1+ depth) :reply-stream (getf current-signal :reply-stream)
|
||||
:payload (list :sensor :loop-error :message (format nil "~a" c) :depth depth)))))))))))
|
||||
#+end_src
|
||||
|
||||
(defvar *default-heartbeat-interval* 60)
|
||||
(defvar *heartbeat-thread* nil)
|
||||
** Heartbeat Mechanism
|
||||
The heartbeat ensures the agent remains "alive" even in the absence of external stimuli, allowing for latent reflection and periodic maintenance. The interval is externalized to the `HEARTBEAT_INTERVAL` environment variable.
|
||||
|
||||
(defun start-heartbeat (&optional (interval *default-heartbeat-interval*))
|
||||
(setf *heartbeat-thread*
|
||||
(bt:make-thread
|
||||
(lambda ()
|
||||
(loop
|
||||
(sleep interval)
|
||||
(inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
|
||||
:name "org-agent-heartbeat")))
|
||||
#+begin_src lisp :tangle ../src/loop.lisp
|
||||
(defun start-heartbeat ()
|
||||
"Starts the background heartbeat thread. Interval is loaded from HEARTBEAT_INTERVAL."
|
||||
(let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60)))
|
||||
(setf *heartbeat-thread*
|
||||
(bt:make-thread
|
||||
(lambda ()
|
||||
(loop
|
||||
(sleep interval)
|
||||
;; inject-stimulus is synchronous for heartbeats, preventing accumulation.
|
||||
(inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
|
||||
:name "org-agent-heartbeat"))))
|
||||
#+end_src
|
||||
|
||||
** Main Entry Point
|
||||
The `main` function initializes the environment, loads skills, and starts the heartbeat. It now includes a graceful shutdown handler for `SIGINT` (Ctrl+C) and uses `DAEMON_SLEEP_INTERVAL` to control its idle rhythm.
|
||||
|
||||
#+begin_src lisp :tangle ../src/loop.lisp
|
||||
(defun main ()
|
||||
"Entry point for the Skeleton MVP."
|
||||
"Entry point for the Skeleton MVP. Handles initialization and graceful shutdown."
|
||||
(let* ((home (uiop:getenv "HOME"))
|
||||
(env-file (uiop:merge-pathnames* ".local/share/org-agent/.env" (uiop:ensure-directory-pathname home))))
|
||||
(when (uiop:file-exists-p env-file) (cl-dotenv:load-env env-file)))
|
||||
|
||||
(initialize-all-skills)
|
||||
(start-heartbeat)
|
||||
(loop (sleep 3600)))
|
||||
|
||||
;; Graceful shutdown handler for SBCL
|
||||
#+sbcl
|
||||
(sb-sys:enable-interrupt sb-unix:sigint
|
||||
(lambda (sig code scp)
|
||||
(declare (ignore sig code scp))
|
||||
(harness-log "SHUTDOWN: SIGINT received. Exiting...")
|
||||
(uiop:quit 0)))
|
||||
|
||||
(let ((sleep-interval (or (ignore-errors (parse-integer (uiop:getenv "DAEMON_SLEEP_INTERVAL"))) 3600)))
|
||||
(loop
|
||||
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*) (return))
|
||||
(sleep sleep-interval))))
|
||||
#+end_src
|
||||
|
||||
@@ -18,8 +18,8 @@ You MUST commit and push the current state of the workspace BEFORE initiating an
|
||||
** 2. Literate Programming (The Single Source of Truth)
|
||||
All system logic and skills MUST be implemented as Literate Org files. Weaving documentation and code together ensures that the "Why" (Architectural Intent) is never separated from the "How" (Implementation).
|
||||
|
||||
** 3. Literate Granularity
|
||||
Every Lisp function, macro, or variable must reside in its own dedicated `#+begin_src lisp` block. This allows for specific narrative explanation for every logical unit.
|
||||
** 3. Literate Granularity (The Function-Block Rule)
|
||||
Every Lisp function, macro, or variable MUST reside in its own dedicated `#+begin_src lisp` block, immediately preceded by its literate explanatory text. This ensures that the logical intent is documented alongside the implementation.
|
||||
|
||||
** 4. Test-Driven Development (Continuous QA)
|
||||
No change is complete without verification. Every new function or macro must be accompanied by a FiveAM test case. You must run the test suite and verify success before considering a task complete.
|
||||
@@ -33,6 +33,9 @@ You are strictly forbidden from drafting a plan or requesting formal approval in
|
||||
** 7. GTD Synchronization (Roadmap Integrity)
|
||||
You are strictly forbidden from considering a task complete without updating `gtd.org`. Every major architectural shift, feature implementation, or refactor MUST be recorded in the project roadmap to ensure technical transparency and historical auditability.
|
||||
|
||||
** 8. Configuration Externalization (Environment-Driven)
|
||||
Source code MUST be strictly free of hardcoded configuration values (e.g., ports, rhythms, timeouts). All such values must be extracted to the environment (via `.env`) and documented in `.env.example`. This ensures portability and security.
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
(defvar *interrupt-flag* nil)
|
||||
(defvar *interrupt-lock* (bt:make-lock "harness-interrupt-lock"))
|
||||
(defvar *heartbeat-thread* nil)
|
||||
|
||||
(defun process-signal (signal)
|
||||
"The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act."
|
||||
@@ -19,31 +20,47 @@
|
||||
(setf current-signal (reason-gate current-signal))
|
||||
(setf current-signal (act-gate current-signal)))
|
||||
(error (c)
|
||||
(harness-log "METABOLISM CRASH: ~a - Initiating Micro-Rollback." c)
|
||||
(rollback-memory 0)
|
||||
(let ((sensor (ignore-errors (getf (getf current-signal :payload) :sensor))))
|
||||
(harness-log "METABOLISM CRASH [~a]: ~a" (or sensor :unknown) c)
|
||||
;; Only rollback on critical errors, not standard tool or loop errors
|
||||
(unless (member sensor '(:loop-error :tool-error :syntax-error))
|
||||
(harness-log "CRITICAL ERROR: Initiating Micro-Rollback.")
|
||||
(rollback-memory 0))
|
||||
(if (or (> depth 2) (member sensor '(:loop-error :tool-error)))
|
||||
(setf current-signal nil)
|
||||
(setf current-signal (list :type :EVENT :depth (1+ depth) :reply-stream (getf current-signal :reply-stream)
|
||||
:payload (list :sensor :loop-error :message (format nil "~a" c) :depth depth)))))))))))
|
||||
|
||||
(defvar *default-heartbeat-interval* 60)
|
||||
(defvar *heartbeat-thread* nil)
|
||||
|
||||
(defun start-heartbeat (&optional (interval *default-heartbeat-interval*))
|
||||
(setf *heartbeat-thread*
|
||||
(bt:make-thread
|
||||
(lambda ()
|
||||
(loop
|
||||
(sleep interval)
|
||||
(inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
|
||||
:name "org-agent-heartbeat")))
|
||||
(defun start-heartbeat ()
|
||||
"Starts the background heartbeat thread. Interval is loaded from HEARTBEAT_INTERVAL."
|
||||
(let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60)))
|
||||
(setf *heartbeat-thread*
|
||||
(bt:make-thread
|
||||
(lambda ()
|
||||
(loop
|
||||
(sleep interval)
|
||||
;; inject-stimulus is synchronous for heartbeats, preventing accumulation.
|
||||
(inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
|
||||
:name "org-agent-heartbeat"))))
|
||||
|
||||
(defun main ()
|
||||
"Entry point for the Skeleton MVP."
|
||||
"Entry point for the Skeleton MVP. Handles initialization and graceful shutdown."
|
||||
(let* ((home (uiop:getenv "HOME"))
|
||||
(env-file (uiop:merge-pathnames* ".local/share/org-agent/.env" (uiop:ensure-directory-pathname home))))
|
||||
(when (uiop:file-exists-p env-file) (cl-dotenv:load-env env-file)))
|
||||
|
||||
(initialize-all-skills)
|
||||
(start-heartbeat)
|
||||
(loop (sleep 3600)))
|
||||
|
||||
;; Graceful shutdown handler for SBCL
|
||||
#+sbcl
|
||||
(sb-sys:enable-interrupt sb-unix:sigint
|
||||
(lambda (sig code scp)
|
||||
(declare (ignore sig code scp))
|
||||
(harness-log "SHUTDOWN: SIGINT received. Exiting...")
|
||||
(uiop:quit 0)))
|
||||
|
||||
(let ((sleep-interval (or (ignore-errors (parse-integer (uiop:getenv "DAEMON_SLEEP_INTERVAL"))) 3600)))
|
||||
(loop
|
||||
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*) (return))
|
||||
(sleep sleep-interval))))
|
||||
|
||||
Reference in New Issue
Block a user