#+TITLE: SKILL: Context Manager (org-skill-context-manager.org) #+AUTHOR: Agent #+FILETAGS: :system:context:scoping: #+PROPERTY: header-args:lisp :tangle ../lisp/system-context-manager.lisp * Overview The Context Manager provides stack-based project focusing. When the agent "focuses" on a project, file paths resolve relative to it and memory queries auto-filter by scope. This enables the agent to work within a bounded context without being distracted by unrelated memory. The core provides the mechanism (=memory-object-scope=, =context-query= with scope parameter). This skill provides the policy — what to focus on, what scope means for each project, and how the stack is managed. * Implementation ** Context Stack ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *context-stack* nil "Stack of context plists. Each plist has :project, :base-path, :scope. Top of stack (car) is the current context.") #+end_src ** *context-max-depth* ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defvar *context-max-depth* 10 "Maximum context stack depth. Prevents runaway pushes.") #+end_src #+end_src ** Context Accessors ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun current-context () "Returns the current context plist, or nil if no context is set." (car *context-stack*)) #+end_src ** current-scope ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun current-scope () "Returns the current scope keyword (:memex/:session/:project). Returns :memex when no context is set (defaults to global scope)." (or (getf (current-context) :scope) :memex)) #+end_src ** current-project ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun current-project () "Returns the current project name, or nil." (getf (current-context) :project)) #+end_src ** current-base-path ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun current-base-path () "Returns the current base path for file resolution, or nil." (getf (current-context) :base-path)) #+end_src ** context-stack-depth ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun context-stack-depth () "Returns the current depth of the context stack." (length *context-stack*)) #+end_src #+end_src ** Stack Operations ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun push-context (&key project base-path (scope :project)) "Pushes a new context onto the stack. When focused on a project: - File paths resolve relative to BASE-PATH - Memory queries filter by SCOPE - :memex scope objects remain visible (always global) Returns the new context plist." (when (>= (context-stack-depth) *context-max-depth*) (log-message "CONTEXT: Stack depth limit reached (~d), refusing push" *context-max-depth*) (return-from push-context (current-context))) (let* ((context (list :project project :base-path base-path :scope scope))) (push context *context-stack*) (log-message "CONTEXT: Pushed ~a (depth ~d)" project (context-stack-depth)) context)) #+end_src ** pop-context ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun pop-context () "Pops the current context, restoring the previous one. Returns the restored context or nil if stack becomes empty." (if *context-stack* (let ((popped (pop *context-stack*))) (log-message "CONTEXT: Popped ~a (depth ~d)" (getf popped :project) (context-stack-depth)) (current-context)) (progn (log-message "CONTEXT: Cannot pop — stack is empty") nil))) #+end_src ** with-context ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defmacro with-context ((&key project base-path (scope :project)) &body body) "Executes BODY within a scoped context, then restores the previous context. Example: (with-context (:project \"passepartout\" :base-path \"/home/user/memex/projects/passepartout\") (context-scoped-query :tag \"bug\"))" `(let ((*context-stack* (cons (list :project ,project :base-path ,base-path :scope ,scope) *context-stack*))) ,@body)) #+end_src #+end_src ** Path Resolution Resolves file paths relative to the current project's base path. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun resolve-path (path) "Resolves a file path relative to the current context. If PATH is absolute, returns it unchanged. If PATH is relative and a base-path is set, merges them. Otherwise returns PATH unchanged." (let ((base (current-base-path))) (if (and base path (not (uiop:absolute-pathname-p path))) (namestring (merge-pathnames path (uiop:ensure-directory-pathname base))) path))) #+end_src ** Memory Scope Filtering Provides scope-aware query access. When a context is active (scope ≠ :memex), queries only return objects whose scope is :memex (global) or matches the current scope. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun context-scoped-query (&key tag todo-state type) "Like context-query but filtered to the current context's scope. :memex-scoped objects are always visible regardless of current scope." (context-query :tag tag :todo-state todo-state :type type :scope (current-scope))) #+end_src ** project-objects ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun project-objects () "Returns all objects scoped to the current project. Includes :memex-scoped objects (global knowledge) plus :project-scoped objects matching the current project." (context-scoped-query)) #+end_src #+end_src ** Project Focus Convenience ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun focus-project (name base-path) "Shortcut: focus on a project by name and base path. Calls push-context with :scope :project." (push-context :project name :base-path base-path :scope :project)) #+end_src ** focus-session ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun focus-session () "Shortcut: enter a session context (ephemeral scope). Objects created in this scope are visible only during the session." (push-context :project "session" :scope :session)) #+end_src ** focus-memex ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun focus-memex () "Shortcut: return to global memex scope. Equivalent to pop-context until stack is empty or :memex context is reached." (loop while (and *context-stack* (not (eq (getf (current-context) :scope) :memex))) do (pop-context))) #+end_src ** unfocus ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun unfocus () "Pop the top context and return to the previous one." (pop-context)) #+end_src #+end_src ** Skill Registration #+begin_src lisp (defskill :passepartout-system-context-manager :priority 90 :trigger (lambda (ctx) (declare (ignore ctx)) nil) :deterministic (lambda (action ctx) (declare (ignore action)) (ignore-errors (when (> (context-stack-depth) 0) nil)) nil)) #+end_src ** Auto-Init: Wire Scope Resolver Registers ~current-scope~ into the core ~*scope-resolver*~ hook so the perceive gate tags ingested objects with the active context scope. #+begin_src lisp (when (boundp '*scope-resolver*) (setf *scope-resolver* #'current-scope)) #+end_src