170 Commits

Author SHA1 Message Date
321d0fa852 ci: Add GitHub Actions (test, lint, release) and issue templates
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-22 19:22:20 -04:00
8d26d55c4f docs: Add critical analysis-informed roadmap (v0.2.0 through v5.0.0)
Working backwards from SOTA parity. Critical analysis of OpenCode,
Claude Code (leaked source), GBrain, and OpenClaw/Hermes.
Every borrowed concept documented with BORROW/REJECT rationale.
All concepts reimplemented in pure Lisp.
2026-04-22 19:20:29 -04:00
1f10e51309 docs: Comprehensive documentation for core skills and README
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
- Policy skill: Add philosophical foundation, invariant priority explanations, code block documentation
- Bouncer skill: Add security vector explanations, flight plan workflow, approval lifecycle
- README: Add architecture diagrams (mermaid), design principles, roadmap details

Each function now has detailed docstrings explaining:
- What it does
- Why it was designed that way
- How it fits into the larger system
2026-04-22 16:58:10 -04:00
2cdd8fe1a4 docs: Comprehensive literate documentation for harness core
- loop.org: Explain metabolic pipeline, error recovery, heartbeat
- reason.org: Document dual-engine architecture (probabilistic + deterministic)
- act.org: Detail actuation, actuator pattern, feedback loop
- perceive.org: Explain signal normalization, async vs sync processing
- manifest.org: Add ASDF system design, boundary contract

Each function now has its own code block with detailed docstrings explaining:
- What the function does
- Why it was designed that way
- How it fits into the larger system
- Parameter descriptions
- Return values
2026-04-22 16:49:27 -04:00
b2d85ac4ae refactor: Reorganize .env.example into logical sections
- Group variables by purpose (Identity, LLM, Daemon, Security, etc.)
- Remove unused variables (LLM_API_KEY, LLM_ENDPOINT, GTD_ENFORCE_INTEGRITY, ORG_AGENT_WEB_PORT, RECIPIENT_ID)
- Add section headers for clarity
2026-04-22 16:02:24 -04:00
cbd786e6b1 fix: Add normalize-plist-keywords and fix RESPONSE unbound bug
- Add normalize-plist-keywords to convert LLM output TYPE -> :TYPE
- Fix reason-gate to validate candidate is proper plist
- Add debugging logs to trace LLM responses
- Fix syntax error (extra paren) in think function
2026-04-22 15:51:03 -04:00
586847bd02 fix: Implement COSINE-SIMILARITY and fix memory persistence serialization
- Replace stub COSINE-SIMILARITY with real dot-product implementation
- Fix memory persistence to convert hash tables to alists before serialization
2026-04-22 15:26:39 -04:00
6bfc95e136 feat: Add make-load-form for org-object serialization
Add make-load-form method to enable standard CL serialization of
org-object structs. This is a prerequisite for proper memory persistence.
2026-04-22 15:25:17 -04:00
620267a8df feat: Add memory persistence functions (save/load-memory-to-disk)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
- Add save-memory-to-disk and load-memory-from-disk to memory.lisp
- Integrate auto-save into heartbeat (every N intervals)
- Load memory on daemon startup, save on graceful shutdown/SIGINT
- Add exports to package.lisp

NOTE: Hash table serialization requires object walker for complex structures.
      Current implementation fails on load due to unreadable objects.
2026-04-22 15:14:18 -04:00
b62b7f1095 fix: protocol validator allows REQUEST without :target if :source is present
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
- Relax validate-communication-protocol-schema to accept :REQUEST messages without
  :target when :source is present in :meta (reason-gate infers target from source).
- This preserves 'equality of clients' — gateways don't duplicate routing logic.
- Add communication-validator to ASD components.
- Fixes TCP CLI gateway integration: clients can now connect and receive responses.
- Verified with test client: 13/13 skills load, Perceive gate processes messages.
2026-04-22 14:47:34 -04:00
aae6938880 fix: Skill loader respects :tangle blocks and breaks circular dep with validator
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 5s
- load-skill-from-org now only collects #+begin_src lisp blocks that have a
  :tangle directive pointing to a runtime .lisp file, excluding tests/ paths.
- validate-lisp-syntax falls back to a basic reader check when
  lisp-validator-validate is not yet fboundp, breaking the circular dependency
  between the harness loader and the validator skill.
- Verified full boot: 13/13 skills load successfully, including the new
  skill-lisp-validator (priority 900) and skill-policy (priority 500).
2026-04-22 13:18:29 -04:00
76040c1f48 feat: Add Lisp Validator skill with 3-phase deterministic gate
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 5s
- Implements structural (O(n) paren balance), syntactic (reader with *read-eval* nil),
  and semantic (whitelist AST walk) validation.
- Exposes :validate-lisp cognitive tool for Probabilistic Engine self-correction.
- Replaces validate-lisp-syntax stub in harness/skills.org with delegation.
- Adds mandatory validation rule to Probabilistic Engine system prompt.
- Refactors org-skill-policy.org with 6 concrete invariants (Transparency, Autonomy,
  Zero-Bloat, Modularity, Mentorship, Sustainability) and explicit override hierarchy.
- Adds Harness Boundary Contract to harness/manifest.org.
2026-04-22 13:12:49 -04:00
6c333af7aa ARCH: Finalize semantic reorganization, skill jailing, and unified CLI
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 4s
2026-04-22 11:38:13 -04:00
60f2c152e0 REORG: Apply semantic directory structure and documentation cleanup
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-21 19:07:32 -04:00
2889c65d28 DOCS: Restore comprehensive philosophical argument to README.org
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-20 19:24:28 -04:00
e49fc45047 DOCS: Finalize README and CHANGELOG for v0.1.0 release
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-20 19:06:32 -04:00
cab0e5a459 RELEASE: OpenCortex v0.1.0 (The Autonomous Foundation)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
- Audited Reactive Signal Pipeline.
- Finalized Unified Envelope & Provider-Agnosticism.
- Completed workspace cleanup and documentation.
- Hardened installer for VM/Docker deployment.
2026-04-20 19:05:37 -04:00
c70f182888 FEAT: Stabilize Unified Envelope Architecture & TUI UX
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
- Fixed background boot crash via --non-interactive flag.
- Implemented robust protocol sanitization (stripped raw streams).
- Refined TUI formatting to display human-readable tool results.
- Fixed opencortex.sh variable shadowing and connection logic.
- Resolved :target field schema validation errors.
2026-04-20 18:19:54 -04:00
5a164363b8 fix(reason): Mandate strict sexp output and implement cognitive extraction safety
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 20:51:48 -04:00
6be28ba790 fix(tui): Definitive repair of parenthesis balance in TUI client
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:41:25 -04:00
b7622feb38 fix(gateway): Total clean-room rewrite of CLI Gateway (verified stable)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 20:36:45 -04:00
0834b4695c fix(protocol): Definitive S-expression restoration and synchronization
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:34:26 -04:00
3e184ad344 fix(protocol): Revert to Hardened S-expressions (Lisp Purity restoration)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:31:06 -04:00
499ef377e6 fix(protocol): Migrate to JSON framing with newline delimiters (resolves desync)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:23:27 -04:00
8db786a33a fix(protocol): Disable pretty-print during IO and handle NIL chat text
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:16:22 -04:00
dfa5ba14f1 fix(protocol): Definitively tangle proto-get in package layer
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 20:10:13 -04:00
bfe35d0f6a fix(protocol): Move proto-get to package layer to resolve circular dependency
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:06:48 -04:00
6a5695310c fix(protocol): Resolve desync by handling HMAC signature in reader and normalizing env vars
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 20:03:19 -04:00
95fecdd64c fix(protocol): Whitelist :CHAT message type in schema validator
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 19:59:26 -04:00
9ae5d03f14 debug(protocol): Log rejected messages in validator
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 19:56:30 -04:00
e2d1d9ac8f fix(protocol): Force uppercase normalization for message types in validator
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 19:47:21 -04:00
5ad02f6f2e fix(protocol): Make validator case-insensitive and normalize actuation keywords
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 19:44:26 -04:00
2149d81c5f fix(gateway): Definitive repair of malformed handler-case lambda-lists
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 19:36:56 -04:00
9a441226a2 fix(gateway): Definitive repair of handler-case syntax
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 19:32:31 -04:00
fb5a5b19cc fix(boot): Implement boot.lock to prevent duplicate daemon race conditions
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 19:19:42 -04:00
f296d1ca1c test(ui): Wait for Brain to boot before launching TUI
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 19:16:58 -04:00
7bd58d7089 fix(boot): Use --eval '(load ...)' instead of --load '(form)' for pre-compilation
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 19:13:58 -04:00
54bc4743fd fix(boot): Correct shell quoting for Lisp pre-compilation form
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 19:10:52 -04:00
89ddfd8ec3 fix(boot): Perform full foreground pre-compilation to prevent FASL race conditions
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 8s
2026-04-19 19:08:59 -04:00
40beb513e9 fix(boot): Remove destructive cache purge from TUI launch to prevent race condition
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 6s
2026-04-19 19:04:17 -04:00
ef5b13dd15 fix(boot): Implement Neural Cache Warming to prevent compilation race conditions
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 19:00:50 -04:00
5d8b59db1d fix(tui): Total clean-room physical rewrite (verified stable)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 4s
2026-04-19 18:49:14 -04:00
6fe1227ae8 fix(gateway): Enable dynamic provider cascading and remove hardcoded models
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 18:37:54 -04:00
e1ce366130 fix(tui): Definitive syntax repair and case-insensitive handling
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 18:37:15 -04:00
21c2d4b8bb fix(act): Ensure tool errors are reported back to the user and fix TUI echo
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 18:17:51 -04:00
68375b93ea fix(tui): Correct parenthesis and add local echo
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 18:11:26 -04:00
be2cbcd9ff fix(tui): Add local echo so user sees their own messages move up 2026-04-19 18:09:21 -04:00
bafc473395 fix(tui): Definitive clean-room rewrite of TUI client (verified syntax)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 18:06:33 -04:00
8b7d4c1b9c fix(tui): Definitive fix for status leakage and error visibility
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 17:53:57 -04:00
199c5d09a1 fix(act): Route tool outputs to CLI and refine assistant conversational prompt
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:43:25 -04:00
bbc32a62b8 fix(reason): Automatically convert :response plists to :CHAT messages
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:41:21 -04:00
63e7e9ce32 fix(gateway): Case-insensitive key lookup in get-nested (OpenRouter response fix)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 17:35:25 -04:00
ec1f4af623 fix(gateway): Repair malformed handler-case syntax in LLM Gateway
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:32:53 -04:00
0f49356886 fix(gateway): Harden response parsing and enable neural debugging (OpenRouter fix)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:31:00 -04:00
346e74ccf8 fix(kernel): Definitively export shared symbols in package.org
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:28:19 -04:00
aa39bbbaa8 fix(reason): Definitive clean-room rewrite of cognition engine (verified syntax)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:26:09 -04:00
3374d27e75 fix(reason): Initialize neural provider cascade (resolves Neural-Dead kernel)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:22:30 -04:00
cce760932e fix(kernel): Enable Universal Reasoning Fallback (resolves Bouncer-hijacking bug)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:11:53 -04:00
d4fb6630d3 fix(kernel): Revert invalid SETF on proto-get to standard GETF
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 17:04:57 -04:00
8bcd07bd45 fix(kernel): Implement robust proto-get and suppress heartbeat noise in CLI
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:58:52 -04:00
63e821ede3 fix(kernel): Aggressive Lisp cache purge and keyword normalization
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:44:46 -04:00
6bb22db181 fix(tui): Definitive parenthesis balance and keyword normalization
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:40:55 -04:00
fc2ab65d45 fix(kernel): Strip literal quotes from actuator environment variables
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 16:36:44 -04:00
92fd3cda14 fix(kernel): Hardened actuator registry and dispatch (case-insensitive, forced keywords)
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:34:31 -04:00
31f963243d fix(kernel): Normalize default actuators to uppercase :CLI
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:30:38 -04:00
72c6032556 fix(protocol): Normalize actuator registration to uppercase :CLI
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 16:28:15 -04:00
813e3c830b fix(kernel): Strip literal quotes from mandatory skill list
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:23:15 -04:00
59140b0e64 fix(kernel): Definitive repair of parenthesis imbalance in context.org
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:21:19 -04:00
7a5c4f0b18 fix(kernel): Definitive quote stripping and env detection
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 16:17:44 -04:00
a17d4e3151 fix(memory): Definitive fix for list-objects-with-attribute parenthesis imbalance
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:05:26 -04:00
ee85e40655 fix(memory): Implement missing list-objects-with-attribute
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 16:03:33 -04:00
1944084426 fix(kernel): Refine jailing logic and bouncer dependencies for cleaner boot
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:56:44 -04:00
60384d4f32 fix(memory): Clean-room rewrite to purge FiveAM syntax errors
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:52:51 -04:00
91adcc7876 fix(actuator): Clean-room rewrite of shell-actuator skill to purge syntax errors
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 15:51:29 -04:00
a9f0d9ab49 fix(skills): Resolve too many colons in package qualifications
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 15:49:54 -04:00
79a3f303cc fix(skills): Comprehensive syntax and symbol repair for all skills
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:47:59 -04:00
655fb09e55 fix(skills): Definitive fix for character literal syntax
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:43:26 -04:00
483aa57aee fix(skills): Correct character literal syntax for comma
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 15:41:59 -04:00
c5bd63e388 fix(skills): Purge backslash corruption and add missing kernel stubs
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:40:44 -04:00
59190f4e44 fix(kernel): Declare shared symbols (*VAULT-MEMORY*, COSINE-SIMILARITY) in package
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:39:59 -04:00
dd2f5c83ce fix(tui): Add Keyword Cleaner to ensure reliable protocol matching
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 15:36:16 -04:00
e8c66c7e4a fix(protocol): Normalize all keywords to uppercase to resolve TUI routing duplicates
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:31:25 -04:00
455a1a62b2 fix(protocol): Synchronize uppercase keywords for TUI and CLI Gateway
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 15:27:33 -04:00
22726047a1 fix(kernel): Allow approved logs and events to pass act-gate without being flagged as blocked
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:23:08 -04:00
8c6a192af1 fix(protocol): Skip leading whitespace in read-framed-message to prevent desync
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 15:19:58 -04:00
d00112156f fix(vault): Define *vault-memory* as opencortex::*vault-memory* to fix Unbound Variable crash on CHAT-MESSAGE
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 15:05:05 -04:00
1719f0b6cf fix(tui): Use get-wide-event and map character keys correctly
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 14:54:15 -04:00
a60cb5d4bf fix(tui): Redraw status-win conditionally and non-block input-win to prevent cursor flickering
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 14:29:18 -04:00
6117793cd9 fix(boot): Use explicit bash invocation to prevent Permission denied on execution
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 14:18:05 -04:00
9444952d81 fix(vault): Properly comment out fiveam test forms
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 13:52:53 -04:00
c9332b5668 fix(tui): Correct croatoan slot name to function-keys-enabled-p
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 13:27:16 -04:00
06361847f8 fix(tui): Correct input focus and cursor positioning
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 13:18:48 -04:00
309b8ee8a7 fix(tui): Surgical parenthesis balance repair for TUI client main loop
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 13:11:52 -04:00
24228e02fe fix(tui): Restore input window cursor focus and rendering
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 13:01:49 -04:00
29a8af32f7 fix(tui): Manually verified parenthesis balance for main loop
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 12:39:57 -04:00
c1d5c14412 fix(tui): Force screen refresh and initialize input prompt
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 12:33:02 -04:00
65d230e502 fix(protocol): Whitelist :STATUS message type in schema validator
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 12:26:44 -04:00
b867c8066f fix(tui): Purge final newline from protocol and disable Lisp debugger for TUI
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 12:18:49 -04:00
c2db51ff37 fix(package): Correct literal newline in export list
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 12:09:38 -04:00
3ee193f12f fix(package): Export read-framed-message for TUI client
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 12:03:05 -04:00
d99166d41a fix(setup): Restore bash variables accidentally evaluated during refactor
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 11:54:38 -04:00
eb33106771 refactor(setup): Implement strict command router to eliminate fallthrough bugs
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 11:47:44 -04:00
6a23c23e89 fix(setup): Inject exit 0 to prevent fallback CLI execution
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 11:44:42 -04:00
1bb2d92f3f fix(setup): Definitively halt script after setup success to prevent CLI fallthrough
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 11:42:22 -04:00
559d4bd6de fix(setup): Correctly exit after setup success to prevent protocol fallthrough
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-19 11:37:23 -04:00
af55fa525e fix(mvp): Resolve protocol desync, TUI scoping, and installer fallthrough
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-19 11:32:06 -04:00
d8236cb2cf fix(mvp): 100% Green Boot, Llama.cpp backend, and setup refinement
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-17 20:25:01 -04:00
3471870ab3 fix(tui): Synchronize TUI client with protocol framing and fix input handling
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-17 20:04:49 -04:00
da38bea182 fix(mvp): Resolve installer fallthrough and TUI connection hang
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-17 19:57:45 -04:00
7c44e00a5f feat(gateway): Upgrade CLI Gateway to high-integrity framed protocol
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-17 19:49:12 -04:00
7430ae24e0 feat(protocol): Implement high-integrity framed message reader
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-17 19:48:44 -04:00
d90cfd5bfe feat(kernel): Automatically start the protocol server on boot 2026-04-17 19:29:41 -04:00
f4051f1244 fix(tui): Correct window constructor to make-instance 'window
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-17 19:20:34 -04:00
a6fabbbc73 fix(setup): Harden bash-to-lisp handoff and resolve persistent syntax errors
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-17 19:10:06 -04:00
458351aa93 fix(setup): Sync literate setup.org with clean re-write of opencortex.sh
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 23s
2026-04-17 19:04:56 -04:00
07f1a746ce fix(setup): Definitively resolve bash syntax errors with clean re-write
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 24s
2026-04-17 19:04:03 -04:00
6f9ec7f45c fix(setup): Restore missing newline after squashed SBCL command
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 27s
2026-04-17 18:55:31 -04:00
d5089697c3 fix(setup): Remove --non-interactive from all SBCL calls to ensure TUI and pre-load work correctly
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 24s
2026-04-17 18:55:03 -04:00
a3430f3430 fix(setup): Definitively squash all multi-line SBCL commands
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 25s
2026-04-17 18:54:06 -04:00
7969a15815 fix(setup): Squash all multi-line SBCL commands into single lines
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 23s
2026-04-17 18:53:48 -04:00
53591e8909 fix(setup): Squash multi-line SBCL command into single line to avoid bash syntax errors
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Has been cancelled
2026-04-17 18:53:08 -04:00
47a5985287 fix(setup): Purge trailing whitespace breaking line continuation
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 23s
2026-04-17 18:51:09 -04:00
d07572c821 fix(boot): Force absolute SKILLS_DIR export and improve onboarding paths
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 24s
2026-04-17 18:45:16 -04:00
f1d231841f fix(setup): Remove invisible trailing spaces that broke bash line continuation
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 24s
2026-04-17 18:38:34 -04:00
63751e7752 fix(setup): Global .env loading, enable interactive SBCL for TUI, and simplified PATH instructions
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 27s
2026-04-17 18:32:30 -04:00
8f771762bc fix(setup): Correct TUI quickload syntax, robust Quicklisp path, and persistent PATH configuration
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 25s
2026-04-17 18:26:32 -04:00
c7ebf2fc93 fix(boot): Final surgical repair of parenthesis nesting and strings
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 22s
2026-04-17 18:17:13 -04:00
c225e167b6 fix(boot): Final surgical repair of parenthesis nesting and removal of duplicated junk
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 22s
2026-04-17 18:06:50 -04:00
06cdb4f269 fix(mvp): Final syntax fixes, TUI system definition, and robust bootstrapper
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 19s
2026-04-17 17:57:19 -04:00
76c3b68abd fix(mvp): Final syntax fixes, PATH reloading for setup, and eliminated duplicate boot
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 25s
2026-04-17 17:55:44 -04:00
d4be2fcdc7 fix(mvp): Final TUI system definition, syntax fixes, and robust bootstrapper
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 20s
2026-04-17 17:49:27 -04:00
c889fe1cea fix(setup): Make SCRIPT_DIR symlink-aware and harden bootstrapper logic
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 17s
2026-04-17 17:38:37 -04:00
88a05e6d73 fix(boot): Final syntax fix for initialize-all-skills and improved boot failure messages
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 34s
2026-04-17 17:31:49 -04:00
6d49e9b04b fix(boot): Point SKILLS_DIR to repo skills/ folder and improve boot failure error messages
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 35s
2026-04-17 17:24:49 -04:00
c6a6495f7b fix(setup): Remove literal \n that caused bash crash
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 29s
2026-04-17 17:17:32 -04:00
47a2ff2a2d fix(setup): Export Merkle/Org-Object symbols and perform synchronous pre-load in setup
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 27s
2026-04-17 17:12:01 -04:00
90c5f7eaf1 fix(setup): Verbose error reporting, strict tangling, and full path logging for brain.log
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 28s
2026-04-17 17:07:30 -04:00
b562b25e9a fix: Robust brain health verification in setup and improved Lisp error logging
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 27s
2026-04-17 17:00:53 -04:00
a78d9bb405 fix: Default Memex Root to /home/user/memex and ensure all PARA directories exist before boot to prevent daemon crash
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 25s
2026-04-17 16:50:32 -04:00
ca28165d50 chore: Remove stray opencortex-tui binary and test_input.txt, add to .gitignore
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 26s
2026-04-17 16:41:31 -04:00
bcb7acd6fb fix: Install missing system dependencies, add ~/.local/bin to PATH, and clone to ~/.opencortex to prevent autocomplete collision
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 27s
2026-04-17 16:39:20 -04:00
5deb4eac5b fix(security): Harden Lisp reader against macro injection in reason loop
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 22s
2026-04-17 16:30:25 -04:00
c9bdf5f070 feat: Add OpenRouter API key prompt to interactive setup 2026-04-17 16:06:08 -04:00
a045c240b6 fix: Ensure setup script prompts read from /dev/tty when piped
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 26s
2026-04-17 15:59:21 -04:00
7228d69dd4 chore: Refactor TUI dependencies and improve setup script
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 21s
2026-04-17 15:39:21 -04:00
cb658d3092 feat: Add user and agent name prompts to interactive setup
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 26s
2026-04-17 14:12:06 -04:00
b15e06a329 feat: Implement interactive setup script and unified opencortex tui command 2026-04-17 14:08:06 -04:00
b143820cb7 docs: Update test plan with interactive setup and tui command 2026-04-17 14:05:20 -04:00
afe1ad6ed4 docs: Append User-Centric Test Plan to MVP Spec 2026-04-17 13:57:43 -04:00
c20d04d18a docs: Transition to AGPLv3 and introduce broad CLA 2026-04-17 13:41:42 -04:00
0debfe5a95 refactor: Switch TUI/daemon communication from JSON to Lisp S-Expressions 2026-04-17 13:36:54 -04:00
47a2cf6478 feat: Implement croatoan-based TUI client and structured CLI gateway 2026-04-17 13:24:10 -04:00
4612c46f0d docs: Add MVP specification in org-mode 2026-04-17 12:55:03 -04:00
927c7272e1 fix: Hardened setup_system with cd to SCRIPT_DIR 2026-04-16 18:01:34 -04:00
2fc4bef3d6 fix: Bulletproof unified script and non-recursive boot loop 2026-04-16 17:18:14 -04:00
ff366e38ef fix: Robust socat client fallback
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-16 16:48:23 -04:00
6275d5e1dc REFAC: Merge installer and entrypoint into unified single-file architecture
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-16 16:45:49 -04:00
51b6651b03 fix: Absolute path for ASDF registry in script 2026-04-16 16:28:34 -04:00
36d1e0e842 feat: Add --boot flag and auto-start logic to entrypoint 2026-04-16 16:07:02 -04:00
5226c246e5 fix: Final robust env loader fix 2026-04-16 16:02:10 -04:00
90ddcdff43 fix: Robust .env loader in entrypoint
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 4s
2026-04-16 15:38:16 -04:00
8ca8faf1de fix: Robust .env export loop in script 2026-04-16 15:33:00 -04:00
08b5ecbd91 fix: Properly export all .env variables before boot 2026-04-16 15:31:11 -04:00
9c37fda9b5 fix: Ensure SKILLS_DIR is exported before boot 2026-04-16 15:29:01 -04:00
23028a3082 fix: Add ASDF registry path to startup logic
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-16 15:24:29 -04:00
7923195a16 feat: Automatically install opencortex command to ~/.local/bin
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-16 14:46:37 -04:00
a917cb5a36 REFAC: Sync literate setup with bulletproof installer logic
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
2026-04-16 14:29:15 -04:00
fbb11a039f chore: Final bulletproofing of installer
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s
2026-04-16 14:24:15 -04:00
0272ed3f61 fix: Remove exec from bootstrapper and robustify tangle loop 2026-04-16 14:22:45 -04:00
bdce5a11ee chore: Installer debug mode and non-interactive defaults 2026-04-16 14:18:46 -04:00
104 changed files with 7901 additions and 3737 deletions

View File

@@ -1,62 +1,76 @@
# opencortex: Neural Engine Configuration
# Core LLM Providers
GEMINI_API_KEY="your_gemini_key_here"
ANTHROPIC_API_KEY="your_anthropic_key_here"
OPENAI_API_KEY="your_openai_key_here"
GROQ_API_KEY="your_groq_key_here"
# opencortex: Environment Configuration Template
# Copy this to .env and fill in your values
# =============================================================================
# IDENTITY
# =============================================================================
MEMEX_USER="YourName"
MEMEX_ASSISTANT="AgentName"
# =============================================================================
# LLM PROVIDERS (OpenRouter recommended as primary)
# =============================================================================
OPENROUTER_API_KEY="your_openrouter_key_here"
OPENAI_API_KEY="your_openai_key_here"
ANTHROPIC_API_KEY="your_anthropic_key_here"
GROQ_API_KEY="your_groq_api_key_here"
GEMINI_API_KEY="your_gemini_key_here"
# Legacy/Default (Optional)
LLM_API_KEY="your_api_key_here"
LLM_ENDPOINT="https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent"
# Cascade order (first available provider wins)
PROVIDER_CASCADE="openrouter,openai,anthropic,groq,gemini-api,ollama"
# Communication Gateways
# =============================================================================
# LOCAL LLM (Ollama - runs offline)
# =============================================================================
OLLAMA_HOST="localhost:11434"
# llama.cpp backend (for local GGUF models)
LLAMACPP_ENDPOINT="http://localhost:8080"
# =============================================================================
# MESSAGING GATEWAYS (optional)
# =============================================================================
TELEGRAM_BOT_TOKEN="your_telegram_bot_token_here"
SIGNAL_ACCOUNT_NUMBER="+1..."
# System 2: Symbolic Constraints
SAFETY_BLOCK_SHELL=true
GTD_ENFORCE_INTEGRITY=true
# Harness Protocol Daemon Configuration
# =============================================================================
# DAEMON CONFIGURATION
# =============================================================================
ORG_AGENT_DAEMON_PORT=9105
ORG_AGENT_WEB_PORT=8080
DAEMON_HOST="0.0.0.0"
HEARTBEAT_INTERVAL=60
DAEMON_SLEEP_INTERVAL=3600
# Outbound Communication Defaults
DEFAULT_ACTUATOR="cli"
SILENT_ACTUATORS="cli,system-message,emacs"
# Core Skill Requirements
# A comma-separated list of skill Org files (without extension) required for boot.
# =============================================================================
# SECURITY
# =============================================================================
SAFETY_BLOCK_SHELL=true
PROTOCOL_ENFORCE_HMAC=false
PROTOCOL_HMAC_SECRET="change-this-to-a-secure-random-string"
# =============================================================================
# BOOTSTRAP
# =============================================================================
MANDATORY_SKILLS="org-skill-policy,org-skill-bouncer"
# Context Management & Peripheral Vision
# =============================================================================
# CONTEXT / MEMORY
# =============================================================================
CONTEXT_SEMANTIC_THRESHOLD=0.75
CONTEXT_LOG_LIMIT=20
# Memex Integration
# Inside Docker, /app/ is the root for consolidated notes
MEMEX_DIR="/memex"
ZETTELKASTEN_DIR="/memex/notes"
SKILLS_DIR="/memex/notes"
# PARA Structure (Consolidated)
INBOX_DIR="/memex/inbox"
DAILY_DIR="/memex/daily"
PROJECTS_DIR="/memex/projects"
AREAS_DIR="/memex/areas"
RESOURCES_DIR="/memex/resources"
ARCHIVES_DIR="/memex/archives"
SYSTEM_DIR="/memex/system"
# Identity Configuration
MEMEX_USER="YourName"
MEMEX_ASSISTANT="AgentName"
RECIPIENT_ID="+1..." # For Signal/Telegram delivery
# Harness Protocol Integrity & Authentication (HMAC-SHA256)
Harness Protocol_ENFORCE_HMAC=false
Harness Protocol_HMAC_SECRET="change-this-to-a-secure-random-string"
# =============================================================================
# MEMEX STRUCTURE
# =============================================================================
MEMEX_DIR="$HOME/memex"
SKILLS_DIR="skills/"
ZETTELKASTEN_DIR="$HOME/memex/notes"
INBOX_DIR="$HOME/memex/inbox"
DAILY_DIR="$HOME/memex/daily"
PROJECTS_DIR="$HOME/memex/projects"
AREAS_DIR="$HOME/memex/areas"
RESOURCES_DIR="$HOME/memex/resources"
ARCHIVES_DIR="$HOME/memex/archives"
SYSTEM_DIR="$HOME/memex/system"

25
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Bug Report
about: Report something that is not working as expected.
---
**Describe the bug**
A clear description of what went wrong.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Run '...'
3. See error
**Expected behavior**
What you expected to happen.
**Environment:**
- OS: [e.g. Debian 12, macOS 14]
- SBCL version: [e.g. 2.4.0]
- OpenCortex version: [e.g. v0.1.0]
**Additional context**
Any other relevant information (logs, stack traces, etc.)

View File

@@ -0,0 +1,22 @@
name: Feature Request
about: Suggest a new feature or enhancement.
---
**Describe the problem**
What problem does this feature solve?
**Describe the ideal solution**
A clear description of what you want to happen.
**Describe alternatives considered**
Any alternative solutions you've considered.
**Additional context**
Any other relevant context (mockups, related issues, etc.)
**Implementation suggestion**
(Optional) If you have thoughts on how to implement this in pure Common Lisp + Org-mode:
- Which skill should own this?
- Should it be a =def-cognitive-tool=, a new skill, or an enhancement to an existing one?

45
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Lint
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
container:
image: ubuntu:latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update && apt-get install -y --no-install-recommends \
git emacs-nox
- name: Check for forbidden patterns
run: |
grep -r "json\." --include="*.lisp" . && \
echo "ERROR: Found JSON usage in Lisp files" && exit 1 || \
echo "OK: No JSON in Lisp files"
- name: Check literate granularity
run: |
find . -name "*.org" -path "./skills/*" -exec grep -L "#+begin_src lisp" {} \; | \
grep -v "CLA\|CONTRIBUTING\|CHANGELOG" && \
echo "WARNING: Some skills lack lisp blocks" || \
echo "OK: All skills have lisp blocks"
- name: Verify .lisp files are generated
run: |
for f in library/gen/*.lisp; do
org="${f%.lisp}.org"
if [ -f "$org" ]; then
: # generated, OK
else
echo "WARNING: $f has no corresponding .org source"
fi
done

31
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Create tarball
run: |
git archive --format=tar.gz --prefix=opencortex-$(git describe --tags) HEAD -o opencortex.tar.gz
- name: Create zipball
run: |
git archive --format=zip --prefix=opencortex-$(git describe --tags) HEAD -o opencortex.zip
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
opencortex.tar.gz
opencortex.zip
generate_release_notes: true

44
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
container:
image: statusoftech/sbcl:2.4.0
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update && apt-get install -y --no-install-recommends \
git curl openssl make automake autoconf gcc clisp python3 python3-pip
- name: Install Quicklisp
run: |
curl -L https://beta.quicklisp.org/quicklisp.lisp -o /tmp/quicklisp.lisp
sbcl --non-interactive \
--load /tmp/quicklisp.lisp \
--eval '(quicklisp-quickstart:install :path "~/quicklisp/")' \
--eval '(ql:add-to-init-file)'
- name: Install ASDF systems
run: |
sbcl --non-interactive \
--eval '(ql:quickload :opencortex)'
env:
HOME: /root
- name: Run tests
run: |
sbcl --non-interactive \
--eval '(ql:quickload :opencortex/tests)' \
--eval '(uiop:quit 0)'
env:
HOME: /root

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ opencortex-server
*.log
*~
\#*#
opencortex-tui
test_input.txt

View File

@@ -1,19 +0,0 @@
#+TITLE: Changelog
#+STARTUP: content
* v0.1.0 - The Autonomous Foundation (2026-04-13)
This is the initial MVP release of the ~opencortex~. It establishes a secure, auditable Lisp kernel for a personal operating system.
** Features
- **Metabolic Pipeline:** Robust Perceive-Reason-Act loop with selective memory rollbacks and graceful shutdown handling.
- **Verification Lock:** Mandatory skill enforcement via environment configuration. System halts if security policies or bouncers fail to load.
- **Foveal-Peripheral Context:** High-resolution focus on active tasks with low-resolution skeletal awareness of the rest of the Memex.
- **The Bouncer:** Last-mile deterministic security gate with Deep Packet Inspection for secrets and network exfiltration.
- **Autonomous Scribe:** Background distillation worker that turns daily journal entries into evergreen Zettelkasten notes.
- **Unified Onboarding:** Single-command installation (~opencortex.sh~) with Docker-first deployment and OS detection.
- **CLI Gateway:** Local TCP socket server and interactive chat client for frictionless first contact.
** Architectural Shift
- Transitioned to **Literate Granularity**: Every function and invariant is now documented in its own Org block.
- **Configuration Externalization:** All timing, thresholds, and identities are now driven by environment variables.
- **Thin Harness Philosophy:** Decoupled the kernel from specific editors or third-party gateways.

View File

@@ -1,71 +0,0 @@
# OPENCORTEX v1.0 Production Environment
FROM debian:bookworm-slim
# Prevent interactive prompts during build
ENV DEBIAN_FRONTEND=noninteractive
# 1. Install System Dependencies
# - sbcl: The Lisp Runtime
# - curl/git/unzip: Standard tools for Quicklisp and binaries
# - default-jre: Required by signal-cli
# - python3/pip: Required for Playwright bridge
# - socat: Required for stateful CLI interaction
RUN apt-get update && apt-get install -y \
sbcl \
curl \
git \
unzip \
default-jre \
libsqlite3-0 \
python3 \
python3-pip \
python3-venv \
emacs-nox \
socat \
&& rm -rf /var/lib/apt/lists/*
# 2. Setup Playwright (High-Fidelity Browsing)
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install playwright \
&& playwright install --with-deps chromium
# 3. Install signal-cli (v0.14.0)
ENV SIGNAL_CLI_VERSION=0.14.0
RUN curl -L https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux.tar.gz | tar xz -C /opt \
&& ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/local/bin/signal-cli
# 4. Install Quicklisp & Pin Distribution
# Pinned to 2026-04-01 for bit-rot resistance.
WORKDIR /root
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp \
&& sbcl --non-interactive \
--load quicklisp.lisp \
--eval '(quicklisp-quickstart:install)' \
--eval '(ql-dist:install-dist "http://beta.quicklisp.org/dist/quicklisp/2026-04-01/distinfo.txt" :prompt nil :replace t)'
# 5. Configure SBCL to load Quicklisp on startup
RUN echo '(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init)))' > /root/.sbclrc
# 6. Setup Application Directory
WORKDIR /app
COPY . /app/projects/opencortex
# 7. Pre-cache Lisp Dependencies
RUN sbcl --non-interactive \
--eval '(push #p"/app/projects/opencortex/" asdf:*central-registry*)' \
--eval '(ql:quickload :opencortex)'
# 8. Environment & Volumes
# The host's memex root should be mounted to /memex
ENV MEMEX_DIR=/memex
VOLUME ["/memex"]
# Default Ports
EXPOSE 9105 8080
# Entrypoint
CMD ["sbcl", "--non-interactive", \
"--eval", "(push #p\"/app/projects/opencortex/\" asdf:*central-registry*)", \
"--eval", "(ql:quickload :opencortex)", \
"--eval", "(opencortex:main)"]

674
LICENSE
View File

@@ -1,21 +1,661 @@
MIT License
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (c) 2026 Amr Gharbeia
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View File

@@ -1,144 +1,407 @@
#+TITLE: OpenCortex: The Conductor of your Life Stack
#+CAPTION: A neurosymbolic AI agent framework for the 100-year Memex
#+ATTR_HTML: :width 800
*opencortex* is a minimalist, extensible AI agent framework designed to manage and continuously organize your personal knowledge base. It transforms a static collection of plaintext notes into a live, programmable [[https://en.wikipedia.org/wiki/Memex][Memex]]—an automated, personalized memory system where humans and AI collaborate in the exact same workspace.
* The Problem with Current AI Agents
The current ecosystem of AI agents (typically built in Python or TypeScript) is overwhelmingly built on architectural choices that prioritize rapid prototyping over long-term reliability, security, and self-modification:
1. *The Format Trap (Markdown & JSON):* Most agents force a painful translation layer. Humans write in Markdown, which lacks a strict Abstract Syntax Tree (AST)—a rigorous, nested representation of data that machines need to parse context reliably. Machines, in turn, output JSON or YAML, which are hostile formats for human thought and note-taking. The result is a fractured workspace where the agent's memory and the human's memory are fundamentally incompatible. Furthermore, because Markdown cannot be efficiently collapsed, agents are forced to consume massive amounts of tokens by reading entire files just to find a single paragraph.
2. *The Language Trap (Python & TypeScript):* Python and TypeScript are fantastic for gluing together APIs or training models, but they are poorly suited for an agent that needs to safely read, write, and execute its own code at runtime. Their underlying structures are complex and opaque, making autonomous self-editing incredibly brittle and dangerous.
3. *The Probabilistic Trap:* Almost all modern agents rely entirely on /probabilistic/ reasoning. We ask an AI model to guess a shell command or write a Python script, and then blindly pipe that output to a terminal. Without a rigorous, /deterministic/ layer to formally verify the model's proposals before execution, these systems are fundamentally unsafe.
** 1. The Format Trap (Markdown & JSON)
Most agents force a painful translation layer. Humans write in Markdown, which lacks a strict Abstract Syntax Tree (AST)—a rigorous, nested representation of data that machines need to parse context reliably. Machines, in turn, output JSON, which is hostile for human thought and note-taking.
The result is a fractured workspace where the agent's memory and the human's memory are fundamentally incompatible. You cannot see what the agent sees. The agent cannot naturally work with your notes.
** 2. The Language Trap (Python & TypeScript)
Python and TypeScript are fantastic for gluing together APIs, but they are poorly suited for an agent that needs to safely read, write, and execute its own code at runtime. Their underlying structures are complex and opaque, making autonomous self-editing incredibly brittle and dangerous.
How do you trust an agent to modify its own Python code when Python's AST is so complex that even human programmers need IDEs to navigate it?
** 3. The Probabilistic Trap
Almost all modern agents rely entirely on /probabilistic/ reasoning. We ask an AI model to guess a shell command or write a Python script, and then blindly pipe that output to a terminal. Without a rigorous, /deterministic/ layer to formally verify the model's proposals before execution, these systems are fundamentally unsafe.
The model might hallucinate a command. It might output valid syntax that still does something dangerous. Without a deterministic gate, there's nothing between the guess and the terminal.
* The Vision: A Modern, Homoiconic Memex
opencortex abandons these fragile paradigms by returning to first principles and embracing two historically powerful technologies: *Org-mode* and *Common Lisp*.
openCortex abandons these fragile paradigms by returning to first principles and embracing two historically powerful technologies: *Org-mode* and *Common Lisp*.
** 1. Org-mode: The Universal Language
Instead of wrestling with Markdown parsers or hiding data in opaque databases, opencortex mandates that *Org-mode is the native AST for both humans and machines.*
** Org-mode: The Universal Language
Org-mode is unique because it seamlessly brings together human-readable prose, structured metadata (properties and tags), lifecycle states (TODO/DONE), and executable code blocks into a single plain-text file. The code is the data, and the data is the interface. When the agent "remembers" a fact or schedules a task, it writes an Org headline. You read exactly what the agent reads.
Instead of wrestling with Markdown parsers or hiding data in opaque databases, openCortex mandates that *Org-mode is the native AST for both humans and machines.*
*The Token Advantage:* Because Org-mode is a strict outline, opencortex never needs to send an entire document to an AI model. It uses *Sparse Trees* to send a high-level table of contents, zooming in only on the specific headline relevant to the task. This drastically reduces token consumption and eliminates context window overflow.
Org-mode is unique because it seamlessly brings together:
- Human-readable prose
- Structured metadata (properties and tags)
- Lifecycle states (TODO/DONE/PLAN)
- Executable code blocks
** 2. Common Lisp: The Engine of Self-Modification
There is a beautiful irony to opencortex: Lisp was invented in 1958 specifically to achieve Artificial Intelligence, and it has been waiting nearly 70 years for /this exact moment/ in computing history.
...all in a single plain-text file. The code is the data, and the data is the interface. When the agent "remembers" a fact or schedules a task, it writes an Org headline. You read exactly what the agent reads.
Lisp possesses a unique property called *Homoiconicity*: the primary representation of the program is also a data structure (nested lists) within the language itself. Because Lisp code /is/ Lisp data, it is trivially easy for an AI to generate, manipulate, and safely evaluate new tools at runtime. This makes Lisp the ultimate, un-brittle language for a "self-writing" agent.
This is not a compromise—it's the design principle. The agent's memory and your memory are the same format, the same file, the same text.
** 3. The Probabilistic-Protodeterministic Loop
opencortex does not let AI models touch your system directly. Instead, it splits cognition into two distinct engines:
- *The Probabilistic Engine (The AI Models):* Provides semantic understanding, multimodal translation, and probabilistic creativity. It looks at your Memex and proposes an action by writing a strictly formatted Lisp s-expression.
- *The Deterministic Engine (Common Lisp):* Provides deterministic logic, physics, and safety. It intercepts the model's Lisp proposal, formally verifies its structure against your security rules, and only executes it if it is mathematically sound.
** Common Lisp: The Engine of Self-Modification
Crucially, the Deterministic engine is *continuously progressive*. Right now, it starts by acting as a strict security bouncer—enforcing rules and bounding the AI's actions. But as the system matures, the Deterministic engine will progressively take over more and more of the actual reasoning, reducing the AI models' involvement to a mere semantic translation layer for the messy outside world. We are moving from a /probabilistic-protodeterministic/ system today, toward a fully autonomous /probabilistic-deterministic/ Lisp machine tomorrow.
There is a beautiful irony to openCortex: Lisp was invented in 1958 specifically to achieve Artificial Intelligence, and it has been waiting nearly 70 years for /this exact moment/ in computing history.
Lisp possesses a unique property called *Homoiconicity*: the primary representation of the program is also a data structure (nested lists) within the language itself. Because Lisp code /is/ Lisp data, it is trivially easy for an AI to generate, manipulate, and safely evaluate new tools at runtime.
This makes Lisp the ultimate, un-brittle language for a "self-writing" agent. The agent doesn't need an AST parser—it can simply read and write lists directly. The agent doesn't need a code generator—it can write Lisp that executes Lisp.
** The Probabilistic-Deterministic Loop
openCortex does not let AI models touch your system directly. Instead, it splits cognition into two distinct engines:
1. *The Probabilistic Engine (Neural/Dynamic):* Provides semantic understanding and dynamic reasoning. It utilizes a **Dynamic LLM Cascade** (OpenRouter, Ollama, Anthropic) to ensure the agent always has a "brain," falling back to local models if cloud services are unavailable.
2. *The Deterministic Engine (Logic/Safety):* Intercepts LLM proposals and formally verifies them against your security rules (the "Bouncer" pattern) before execution.
#+begin_src mermaid
flowchart LR
subgraph Probabilistic["Probabilistic Engine (LLM)"]
LLM[LLM Call]
end
subgraph Deterministic["Deterministic Engine (Skills)"]
Policy[Policy Skill<br/>Constitutional invariants]
Bouncer[Bouncer Skill<br/>Security vectors]
Validator[Lisp Validator<br/>Structural verification]
end
subgraph Actuation["Actuation"]
Shell[Shell Actuator]
TUI[TUI Client]
Emacs[Emacs Gateway]
end
LLM -->|Proposes action| Deterministic
Policy -->|Checks| Bouncer
Bouncer -->|Verifies| Validator
Validator -->|Approves| Actuation
Actuation -->|Feeds back| LLM
#+end_src
* Architecture: Thin Harness, Fat Skills
To guarantee long-term stability, opencortex enforces a strict architectural boundary inspired by the "thin harness, fat skills" philosophy.
To guarantee long-term stability, openCortex enforces a strict architectural boundary inspired by the "thin harness, fat skills" philosophy.
** The Minimalist Harness
The Lisp microkernel does almost no actual "work." It is a thin, unbreakable harness strictly responsible for three things:
1. *The Memory:* Maintaining the live graph of your Memex in RAM.
2. *The Communication Protocol:* Managing the secure bridge between the agent and the outside world. While power users can connect natively via Emacs or Vim, the vast majority of users will interact with opencortex exclusively through chat clients (like Telegram, Signal, or Matrix), web dashboards, or a Terminal UI (TUI). The harness doesn't care; it just securely routes the messages.
3. *The Cognitive Cycle:* Moving signals through the Perceive -> Probabilistic -> Deterministic -> Dispatch pipeline.
Everything else—AI routing, vector embeddings, shell execution, or web browsing—is pushed entirely out of the harness and into *Fat Skills*.
The Lisp microkernel is a thin, unbreakable harness strictly responsible for:
| Layer | Responsibility | Examples |
|-------|----------------|----------|
| *Perceive* | Normalize sensory input | CLI parsing, Emacs events, heartbeats |
| *Reason* | Bridge neural and deterministic | LLM dispatch, response parsing, skill routing |
| *Act* | Execute approved actions | Shell commands, tool calls, UI output |
| *Memory* | Live object store | Org-object graph, snapshots, rollback |
What the harness does /not/ contain:
- Policy rules (those are skills)
- LLM integrations (those are skills)
- Domain-specific functionality (those are skills)
** Literate, Single-File Skills
In standard agent frameworks, adding a new capability (like "Search the Web") requires creating a sprawling folder with a Python script, a JSON configuration file, and a separate text file for the AI prompt. This creates massive structural bloat.
In opencortex, a Skill is simply a *single .org file*.
In openCortex, a Skill is simply a *single .org file* containing everything:
- The documentation (prose explaining the skill's purpose)
- The AI instructions (how the LLM should use this skill)
- The deterministic code (Lisp that verifies/proposes actions)
Using *Literate Programming*, this single file contains everything:
- The human-readable documentation and architectural intent.
- The system prompt instructions for the Probabilistic Engine.
- The deterministic Lisp code for the Deterministic engine's safety checks.
- The actual execution logic.
When the system boots, it compiles these skills directly into the live Lisp image. Skills are hot-reloadable without restarting the daemon.
When the system boots, it parses these single files, mathematically proves their dependencies, and compiles them directly into the live Lisp image.
#+begin_src mermaid
flowchart TD
subgraph Skill["Skill: policy.org"]
Docs["Documentation<br/>'This skill enforces...'"]
AI["AI Instructions<br/>'When the user asks about...'"]
Code["Deterministic Code<br/>'(defun policy-check-...)'"]
end
** The Anatomy: Three Data Stores
The agent's "mind" is not a transient chat session; it is a durable, stateful architecture consisting of three layers:
1. *The Linguistic Substrate (Plaintext Files):* The human-readable Source of Truth on your hard drive. You can edit these files in any text editor, and the agent will instantly perceive the changes.
2. *The Lisp Memory (RAM):* The "Active Brain," a live, threaded graph of Lisp objects representing every headline, paragraph, and tag in your Memex. It allows the agent to navigate your life instantly without constantly re-reading files.
3. *The Telemetry Store (External):* A high-volume database for sub-deterministic sensory data (e.g., smart home logs or system metrics), which the agent monitors and distills.
subgraph Harness["Harness Core"]
Package["package.lisp"]
Loop["loop.lisp"]
Perceive["perceive.lisp"]
Reason["reason.lisp"]
Act["act.lisp"]
end
** The Psychology: The 2x2 Cognitive Matrix
The agent operates on a matrix that balances cognitive speed with cognitive state:
| | Probabilistic (Neural/Intuitive) | Deterministic (Deterministic/Logical) |
| :--- | :--- | :--- |
| Foreground (Active) | *The Interface:* Fast AI models for conversation, multimodal ingestion, and semantic understanding. | *The Steward:* Lisp engine that safely retrieves requested data from the Memex and enforces security rules while the Interface keeps you engaged. |
| Background (Passive) | *The Editor:* Deep AI models finding hidden patterns while you sleep. | *The Librarian:* Lisp engine continuously maintaining data integrity and filing away loose notes. |
** The Physiology: Five Core Processes
1. *Perception:* Automatically vectorizes your input and sets the "Foreground Focus" so the agent knows exactly what you are looking at or talking about.
2. *Reasoning:* Uses Lisp-native logic to reconcile contradictions and enforce the physics of the Memex.
3. *Distillation:* A Background loop that reads your chronological daily logs and automatically extracts concepts into permanent, evergreen notes.
4. *Reflection:* A heartbeat-driven process that finds forgotten links and maintains the structural health of the system.
5. *Sensation:* A converter that monitors the raw flood of telemetry data and turns significant anomalies into actionable TODO items on your list.
* The Ecosystem: Core Skill Groups
Because the harness is deliberately thin, every capability of opencortex is implemented as a single-file Literate Skill. This allows you to hot-reload, modify, or completely remove features on the fly without restarting the core environment.
The ecosystem is divided into five primary skill groups:
** 1. Gateways (How you talk to the agent)
The agent meets you where you are. While it natively integrates with text editors, it features standalone gateway skills for modern interfaces.
- *Chat Gateways:* Interact securely from your phone via clients like Matrix, Signal, or Telegram.
- *Web & TUI Dashboards:* High-level visual overviews of your agent's background processes and telemetry.
** 2. Cognition & Memory (How the agent thinks)
- *Model Routing:* Dynamically routes requests to the best available Probabilistic model (e.g., Anthropic, OpenAI, Local Llama) based on task complexity or privacy needs.
- *Peripheral Vision & Embeddings:* Manages the vectorization of your notes, ensuring the agent retrieves semantically relevant context via sparse trees.
- *The Ontology Scribe:* Centralizes all rules regarding Org, GTD, and Org-Roam parsing into a single background subroutine, eliminating parser confusion across the codebase.
** 3. Actuators (How the agent affects the world)
- *The Shell Actuator:* Safely executes whitelisted terminal commands to interact with the host OS.
- *The Playwright Bridge:* Grants the agent the ability to spin up a headless browser, navigate the web, read documentation, and interact with web applications.
** 4. Security & Alignment (How the agent stays safe)
- *Formal Verification:* The mathematical gatekeeper that proves a proposed action is safe (e.g., ensuring file writes are confined strictly to your Memex directory) before execution.
- *The Credentials Vault:* A secure, masked enclave that prevents AI models from ever reading your raw API keys or .env files.
** 5. Background Subroutines (The Autonomous Workers)
- *The Journal Scribe:* Periodically distills messy chronological logs into clean, permanent notes.
- *The Gardener:* A heartbeat-driven worker that flags broken links, finds orphaned ideas, and maintains the structural health of your Memex.
* Quick Start (The Zero-to-One Experience)
opencortex can be installed and booted with a single command. The unified entrypoint script will detect your OS, offer to install Docker if missing, interactively gather your API keys, and launch the autonomous kernel in the background.
#+begin_src bash
curl -fsSL https://raw.githubusercontent.com/gharbeia/opencortex/main/opencortex.sh | bash
Code --> |Compiles into| Harness
Harness --> |Runs| Pipeline
Pipeline --> |Feeds| Skill
#+end_src
After installation, simply type `opencortex` in your terminal to start chatting with your autonomous brain.
** The Metabolic Pipeline
For power users who wish to run the agent natively (Baremetal), please refer to the [[file:literate/setup.org][setup.org]] literate documentation.
Every signal in openCortex moves through the same three-stage pipeline:
* The Evolutionary Roadmap (v0.1.0 to v4.0.0+)
1. *Perceive:* Normalize raw input into a standardized Signal
2. *Reason:* Generate a proposal via LLM, verify via skills
3. *Act:* Execute the approved action, generate feedback
** v0.1.0: The Autonomous Foundation (Current Release)
The initial MVP that establishes a secure, auditable Lisp kernel for a personal operating system. It features a robust metabolic pipeline, mandatory skill enforcement, and background distillation.
#+begin_src mermaid
sequenceDiagram
participant User
participant Gateway
participant Perceive
participant Reason
participant Act
participant User
** v1.0.0 (Phase 2.5): The Verified Wrapper (Current Target)
At this stage, opencortex achieves feature parity with State-of-the-Art autonomous agents (like Devin or SWE-agent) but with Lisp-grade mathematical security.
- *The Tools are External:* The agent uses a standard bash shell, a headless browser (via Playwright), and standard file I/O.
- *The Safety is Internal:* The Bouncer and Formal Verification gates mathematically prove actions are safe before piping them to external tools.
- *The Result:* An autonomous agent capable of end-to-end software engineering, web research, and system administration, running securely and locally.
User->>Gateway: "Write a note about X"
Gateway->>Perceive: Raw message
Perceive->>Perceive: Normalize to Signal
Perceive->>Reason: Signal
Reason->>Reason: LLM generates proposal
Reason->>Reason: Skills verify proposal
Reason->>Act: Approved action
Act->>Act: Execute action
Act->>Reason: Feedback signal
Reason->>Perceive: New signal
Perceive->>Gateway: Response
Gateway->>User: "Done"
#+end_src
** v2.0.0 (Phase 3): The Cannibalization
Replacing string-based tool wrappers with native Lisp data structures to eliminate LLM fragility.
- *Cannibalizing the Browser:* Ingesting the DOM as a native Lisp AST rather than fighting with Playwright scripts.
- *Cannibalizing the Shell & Editor:* Moving from bash execution to native OS API bindings. Emacs becomes a viewport for the live AST, not a master.
- *The Result:* The LLM no longer has to guess at messy `stdout` or raw HTML strings; it manipulates deterministic data structures directly.
** The Skill Registry
** v3.0.0 (Phase 4): True Symbolic Determinism
The great inversion. The Lisp engine takes the wheel, and the LLM is relegated to translation.
- *The Semantic Translator:* The LLM exclusively translates unstructured human intent (natural language, images) into strict Lisp S-expressions.
- *Deterministic Planning (The Solver):* The core reasoning engine uses formal logic, graph traversal, and constraint solving to plan and execute workflows.
- *Self-Correcting Syntax:* The Lisp engine catches and repairs hallucinated syntax errors without consulting the LLM.
Skills are discovered, sorted by dependency, and loaded at boot:
#+begin_src mermaid
flowchart LR
subgraph Discovery["Skill Discovery"]
Scan["Scan skills/ directory"]
Sort["Topological sort by DEPENDS_ON"]
end
subgraph Loading["Skill Loading"]
Validate["Validate syntax"]
Jail["Jail in package namespace"]
Register["Register in catalog"]
end
Scan --> Sort --> Validate --> Jail --> Register
#+end_src
* The Three Data Stores
openCortex maintains three distinct representations of your knowledge:
| Store | Format | Location | Purpose |
|-------|--------|----------|---------|
| *Source of Truth* | Plaintext .org files | `~/memex/` | Human-readable, version-controlled |
| *Active Brain* | RAM (Lisp hash tables) | Memory | Fast, live, queryable |
| *Snapshots* | Binary .snap files | `~/.opencortex/` | Crash recovery, rollback |
The Active Brain is built from the Source of Truth on boot and kept in sync via:
- Buffer updates from Emacs (when you edit)
- Heartbeat snapshots (periodic persistence)
- Graceful shutdown saves
* The Evolutionary Roadmap
openCortex's roadmap is designed working backwards from SOTA parity (V 1.0.0), guided by a critical analysis of four reference systems: OpenCode, Claude Code (leaked source), GBrain, and OpenClaw/Hermes. Every borrowed concept is reimplemented in pure Lisp. Every rejected pattern is documented.
** Non-Negotiable Identity
- Pure Common Lisp + Org-mode. No JSON. No YAML. No external databases.
- Single-address-space memory (Lisp hash tables in RAM — we *are* the memory).
- "Thin harness, fat skills" — complexity lives at the edges, not the kernel.
- One agent composed of many skills. No sub-agent topologies.
- Plists everywhere — homoiconic communication between all components.
*** OpenCode: Borrowed / Rejected
| Feature | Decision | Rationale |
|---------|----------|-----------|
| Permission filtering before LLM sees tools | BORROW | Hook into =generate-tool-belt-prompt= to exclude denied tools. We have =:guard= but no pre-filter. |
| Hook system (session start/end) | BORROW | Already designing event-orchestrator. Expose via =#+HOOK:= properties. |
| Skills with YAML frontmatter | REJECT | Our Org-mode =:PROPERTIES:= + =#+FILETAGS= already do this. |
*** Claude Code: Borrowed / Rejected
| Feature | Decision | Rationale |
|---------|----------|-----------|
| ULTRAPLAN / structured task decomposition | BORROW (reimplement) | LLM already generates plist actions. Add task-tree skill that decomposes into Org-mode headline DAGs with terminal states. |
| 43 integrated tools | BORROW (approach) | Start with ~3. Build more as skills. Keep =def-cognitive-tool= pattern. |
| 4-tier permission chain (ask/allow/deny) | BORROW (concept) | Three-tier per-tool permission: ask/allow/deny stored in org-objects. |
| Multi-agent hub-and-spoke topology | REJECT | We have one agent. Concurrency via bordeaux-threads (shared memory). Skills ARE the specialization — intra-process, not inter-process. |
| Mailbox pattern for dangerous ops | REJECT | Jailed skill packages + Policy skill already provide isolation. Bouncer gate satisfies "worker can't self-approve". |
*** GBrain: Borrowed / Rejected
| Feature | Decision | Rationale |
|---------|----------|-----------|
| RESOLVER.md intent routing | BORROW (concept) | =find-triggered-skill= already does this. Enhance with multi-skill triggers for complex intents. |
| Three search modes (keyword, hybrid, direct) | BORROW | Keep keyword + direct. Hybrid/vector via local Ollama embeddings — no external DBs. |
| Memory segmentation (brain/agent/session) | BORROW (concept) | Extend org-object with =:scope= property: =:memex= (permanent), =:session= (ephemeral), =:project= (scoped). |
| 20+ cron jobs for background work | BORROW (concept) | Heartbeat already does this. Enhance with Event Orchestrator's cron registry — pure Lisp. |
| Sub-agent model routing for cost | BORROW (concept) | Our =*model-selector-fn*= already selects models. Extend to route by complexity tier. |
| Postgres + pgvector | REJECT | Single-address-space hash tables. No external databases. |
*** opencortex-contrib: Integrate / Reject
| Skill | Decision | Rationale |
|-------|----------|-----------|
| self-fix + lisp-repair | INTEGRATE | Merge into =org-skill-self-edit=. Our memory has snapshot/rollback. Add =repair-file= as cognitive tool. |
| event-orchestrator | INTEGRATE | Merge hooks + cron + routing into ONE skill. Our loop has no unified orchestration. |
| formal-verification | INTEGRATE | =def-invariant= macro + =verify-action-formally= belong in =org-skill-policy.org= as additional checks. |
| engineering-standards | INTEGRATE | Git-clean-p gate + "Commit Before Modify" belong in Policy. |
| sub-agent-manager | REJECT | Redundant with BT threads. Our =defskill= pattern (trigger + probabilistic + deterministic) is intra-process specialization — same goal, zero process overhead. |
| embedding-generator | BORROW | Ollama embeddings for semantic search — no external vector DB. |
| playwright + web-research | DEFER | V 0.5.0. Browser automation via Python bridge. |
** Version Roadmap
*** v0.1.0: The Autonomous Foundation — CURRENT RELEASE ✅
The secure, auditable Lisp kernel. All core infrastructure in place.
| Component | Status | Notes |
|-----------|--------|-------|
| Perceive-Reason-Act pipeline | ✅ | 3-stage metabolic loop |
| Skills engine with jailed loading | ✅ | defskill, topological sort, hot-reload |
| Policy skill (6 invariants) | ✅ | Transparency, Autonomy, Bloat, Modularity, Mentorship, Sustainability |
| Bouncer skill | ✅ | Command whitelist guard functions |
| Memory (org-object + Merkle) | ✅ | Hash tables, snapshots, rollback |
| Lisp validator skill | ✅ | Syntax validation before eval |
| Scribe + Gardener skills | ✅ | Heartbeat-driven distillation + audit |
| LLM gateway (OpenRouter + Ollama) | ✅ | Provider cascade |
| Shell actuator | ✅ | Safe command execution |
| Emacs bridge via Swank | ✅ | Point/buffer updates |
| FiveAM test suite | ✅ | Memory, boot, pipeline, act, communication |
| Credentials vault | ✅ | Encrypted storage |
*** v0.2.0: Self-Improvement + Local LLMs — NEXT
Priority: Self-editing is the foundation of all growth. Full org-mode manipulation makes the agent a true Emacs citizen.
| Feature | Source | Implementation |
|---------|--------|----------------|
| org-skill-self-edit (self-modification) | contrib self-fix + lisp-repair | Hook into =:syntax-error= events. Deterministic: auto-balance parens. Probabilistic: LLM surgical fix. Memory rollback on failure. |
| org-skill-emacs-edit (full org manipulation) | Own need | Read org buffers, parse AST, create/update/delete headlines, set properties, manage TODO, handle links. Uses org-element. |
| Local vector search (Ollama embeddings) | contrib embedding-generator | =generate-embeddings= via Ollama. Add =:vector= to org-object. Semantic search with cosine similarity. |
| Tool permission tiers (ask/allow/deny) | Claude Code | Per-tool permission plist in org-object. =generate-tool-belt-prompt= filters denied tools. |
| Skill hot-reload (=:reload-skill= tool) | Own need | Swap compiled skill files without breaking sockets. |
*** v0.3.0: Event Orchestration + Context Awareness
Priority: Unified control plane, deep project understanding before complex work.
| Feature | Source | Implementation |
|---------|--------|----------------|
| org-skill-event-orchestrator (hooks+cron+routing) | contrib event-orchestrator | Merge *hook-registry* + *cron-registry* + complexity classifier. Hooks via =#+HOOK:=. Three tiers: =:REFLEX= (no LLM), =:COGNITION= (light LLM), =:REASONING= (full LLM). |
| org-skill-context-manager (project scoping) | contrib context-manager | Stack-based context. =push-context= / =pop-context=. Path resolution relative to current context. |
| Memory scope segmentation | GBrain | =:scope= on org-objects: memex/session/project. Scope-aware retrieval. |
| Model-tier routing (cost optimization) | GBrain | Heartbeat → smallest model. User input → medium. Complex reasoning → large. |
| Slash commands (TUI ergonomics) | Own need | =M-x= style command palette. =/-= prefix. Commands defined in org-mode. |
*** v0.4.0: Long-Horizon Planning + Git Workflows
Priority: Real engineering work spans dozens of steps. Structured tracking, failure handling, course correction.
| Feature | Source | Implementation |
|---------|--------|----------------|
| org-skill-long-horizon (task tree DAG) | Claude Code ULTRAPLAN | Decompose tasks into Org-mode headline trees. Terminal states: =:done= / =:blocked= / =:stuck=. Parent summarises children. Branch pruning. |
| org-skill-git-steward (version control) | contrib git-steward | Status, diff, commit, push, branch. Policy enforces commit-before-modify. |
| TDD runner integration | contrib tdd-runner | FiveAM on file save. =:test-failure= events. Hook into self-fix for auto-repair. |
| Deep Emacs integration | Own need | Full org-agenda awareness. Clock time, refile, archive. |
*** v0.5.0: Creator + Architect + GTD
Priority: Agent bootstraps itself. Creates skills autonomously, designs projects from PRDs, tracks work.
| Feature | Source | Implementation |
|---------|--------|----------------|
| org-skill-creator (autonomous skill generation) | contrib creator | LLM drafts complete skill org-file. Mandatory: syntax validation → jail-load → test → register. |
| org-skill-architect (PRD → PROTOCOL) | contrib architect | Scan =:STATUS: FROZEN= PRDs. Generate Phase B PROTOCOL. |
| org-skill-gtd (project tracking) | contrib gtd | Full GTD cycle. org-gtd v4.0 DAG (=:TRIGGER:=, =:BLOCKER:=). |
| Consensus loop (multi-model agreement) | contrib consensus | Run multiple providers, compare results, detect disagreements. |
| Web research (Playwright browsing) | contrib playwright | Headless Chromium via Python bridge. Gemini Web UI automation. |
*** v1.0.0: SOTA Parity
Feature-complete agent, competitive with commercial agents. All borrowed concepts reimplemented in pure Lisp.
| Area | Status | Notes |
|------|--------|-------|
| Self-improvement | ✅ v0.2.0 | Self-edit + lisp-repair = Claude Code self-debug parity |
| Planning | ✅ v0.4.0 | Task tree DAGs = ULTRAPLAN equivalent |
| Tool ecosystem | 🟡 v0.4.0 | 10+ tools (expand from 3) |
| Context window | ✅ v0.3.0 | Semantic search + scope segmentation |
| Safety | ✅ v0.1.0 | 6 Policy invariants + formal verification |
| Multi-step tasks | ✅ v0.4.0 | Task trees with terminal states |
| Code editing | ✅ v0.2.0 | Full file read/write via org manipulation |
| Memory | 🟡 v0.2.0 | Add vector recall to org-object |
| Emacs integration | ✅ v0.2.0 | Full org-mode control — exceeds Claude Code |
| Autonomy | ✅ v0.1.0 | 100% local capable (Ollama) — exceeds Claude Code |
*** v2.0.0: Lisp Machine Emergence
The agent moves from "using Lisp" to "being Lisp."
| Feature | Implementation |
|---------|----------------|
| Lisp editor (Lish) | Org-mode as IDE. Org-babel for interactive evaluation. Full REPL in TUI. |
| Shell replacement (Lish) | Lisp-based shell that speaks plists. Org-mode buffers as file system. |
*** v3.0.0: Neurosymbolic Maturity
| Feature | Implementation |
|---------|----------------|
| Deterministic planner | Planner as pure Lisp function. No LLM for scheduling. |
| Self-correcting gates | Gates learn from false positives (user override patterns). |
*** v4.0.0: AI Stack Internalized
| Feature | Implementation |
|---------|----------------|
| Llama.cpp in Lisp | FFI binding to llama.cpp. No Python. |
| Weights as sexps | Neural weights as Lisp data structures. |
*** v5.0.0: True Agency
| Feature | Implementation |
|---------|----------------|
| World models | Agent builds predictive models of user behavior, project dynamics, system state. |
| Temporal reasoning | The agent reasons about time: scheduling, deadlines, elapsed duration. |
| Goal persistence | Goals survive restarts. Long-term projects tracked in org-objects. |
** Design Principles
** 1. Radical Transparency
If you can't explain it, you can't do it. Every action must be auditable. Hidden reasoning is forbidden.
** 2. Autonomy First
Dependency on proprietary systems is debt. Prefer local, offline-capable solutions.
** 3. Zero Bloat
Complexity must be earned, not anticipated. The harness must remain minimal.
** 4. Modularity
The kernel must survive even if all skills fail. Complexity belongs at the edges.
** 5. Mentorship
Teaching is the highest form of assistance. Every action should increase capability.
** 6. Sustainability
Build for the 100-year horizon. Design for offline operation, local inference.
* Contributing
See [[file:docs/CONTRIBUTING.org][CONTRIBUTING.org]] for the Literate Granularity standard and skill creation guidelines.
* License
openCortex is released under the [[file:LICENSE][AGPLv3 license]].
See [[file:CLA.org][CLA.org]] for the Contributor License Agreement.

View File

@@ -1,55 +0,0 @@
# OpenCortex v0.1.0 User Manual
OpenCortex is a neurosymbolic AI agent designed for autonomous Memex maintenance. It combines the probabilistic power of Large Language Models with the deterministic safety of Common Lisp and the structured clarity of Org-mode.
## 1. Quick Start
Install and boot OpenCortex with a single command:
```bash
curl -fsSL https://raw.githubusercontent.com/gharbeia/opencortex/main/opencortex.sh | bash
```
Once installed, simply run `opencortex` to start the interactive CLI.
## 2. The Core MVP Skills
The v0.1.0 release includes the following essential skills:
### Safety & Integrity
* **System Policy:** Enforces core invariants (Sovereignty, Transparency).
* **The Bouncer:** Inspects all proposed actions and blocks high-risk operations.
* **Protocol Validator:** Ensures communication integrity.
### Cognitive Kernel
* **LLM Gateway:** Routes requests to your preferred provider (Gemini, Anthropic, etc.).
* **Peripheral Vision:** Manages context and retrieves relevant notes via Sparse Trees.
* **Memory Steward:** Maintains the live graph of your Org-mode Memex.
* **Credentials Vault:** Securely stores your API keys.
### Interaction & Actuation
* **CLI Gateway:** The primary interface for chatting with your agent.
* **Shell Actuator:** Allows the agent to perform safe system side-effects.
### Autonomous Services
* **The Scribe:** Automatically distills your daily chronological logs into structured notes.
* **The Gardener:** Proactively repairs broken links and flags orphaned nodes.
## 3. Basic Usage
### Chatting
Type natural language messages into the CLI. The agent will perceive your intent, consult its Memory, and propose actions.
### Memex Maintenance
OpenCortex monitors your `daily/` directory. Use the Scribe to distill your thoughts:
`User: Distill my notes from yesterday.`
### Safety Approvals
When the Bouncer intercepts a high-impact action, it will create a "Flight Plan" in your Memex. You must mark it as `APPROVED` before the agent proceeds.
## 4. Configuration
All configuration is stored in the `.env` file in your installation directory. You can update your API keys, change your Assistant's name, or modify the mandatory skill list there.
---
*OpenCortex: The Conductor of your Life Stack.*

View File

@@ -1,46 +0,0 @@
#!/bin/bash
# opencortex: Bare Metal Installation Script
# This script sets up the opencortex daemon on a Linux host (Debian/Fedora).
set -e
echo "--- opencortex: Bare Metal Installation ---"
# 1. Check Dependencies
echo "[1/4] Checking dependencies..."
for cmd in sbcl curl git ripgrep; do
if ! command -v $cmd &> /dev/null; then
echo "Error: $cmd is not installed. Please install it first."
exit 1
fi
done
# 2. Setup Quicklisp
if [ ! -d "$HOME/quicklisp" ]; then
echo "[2/4] Quicklisp not found. Installing..."
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --non-interactive --load quicklisp.lisp --eval '(quicklisp-quickstart:install)'
rm quicklisp.lisp
echo "Quicklisp installed."
else
echo "[2/4] Quicklisp already installed."
fi
# 3. Build standalone binary
echo "[3/4] Building standalone binary..."
PROJECT_ROOT=$(pwd)/../..
sbcl --non-interactive \
--eval "(push \"$PROJECT_ROOT/\" asdf:*central-registry*)" \
--eval "(ql:quickload :opencortex)" \
--eval "(asdf:make :opencortex)"
echo "Binary built: $PROJECT_ROOT/opencortex-server"
# 4. Instructions for Systemd
echo "[4/4] Installation complete."
echo ""
echo "To run as a systemd service:"
echo "1. Edit opencortex.service to set correct paths."
echo "2. sudo cp opencortex.service /etc/systemd/system/"
echo "3. sudo systemctl daemon-reload"
echo "4. sudo systemctl enable --now opencortex"

View File

@@ -1,18 +0,0 @@
[Unit]
Description=opencortex: Probabilistic-Deterministic Lisp Machine Kernel
After=network.target
[Service]
Type=simple
# Update User and WorkingDirectory to match your local setup
User=amr
WorkingDirectory=/home/amr/.openclaw/workspace/memex/5_projects/opencortex
ExecStart=/home/amr/.openclaw/workspace/memex/5_projects/opencortex/opencortex-server
Restart=always
RestartSec=10
# Environment variables can be loaded from the .env file
EnvironmentFile=/home/amr/.openclaw/workspace/memex/5_projects/opencortex/.env
[Install]
WantedBy=multi-user.target

View File

@@ -1,44 +0,0 @@
FROM debian:bookworm-slim
# Install SBCL, ripgrep, and build dependencies
RUN apt-get update && \
apt-get install -y sbcl build-essential curl git ripgrep libsqlite3-dev lynx python3 python3-pip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Install Quicklisp globally
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp && \
sbcl --non-interactive \
--load quicklisp.lisp \
--eval '(quicklisp-quickstart:install :path "/opt/quicklisp")' \
--eval '(ql-util:without-prompting (ql:add-to-init-file))' && \
rm quicklisp.lisp
# Set up the working directory
WORKDIR /app
# Copy source code and system definition
COPY opencortex.asd /app/
COPY src/ /app/src/
# Ensure we aren't using a stale binary from the host
RUN rm -f /app/opencortex-server
# Build the standalone binary natively inside the container
# This ensures GLIBC compatibility with the runtime environment.
RUN sbcl --non-interactive \
--eval '(push "/app/" asdf:*central-registry*)' \
--eval '(ql:quickload :opencortex)' \
--eval '(asdf:make :opencortex)'
# Ensure the binary is executable
RUN chmod +x /app/opencortex-server
# Expose the communication protocol and Web Dashboard ports
EXPOSE 9105 8080
# The app expects the memex to be mounted here
VOLUME /memex
# Run the natively compiled standalone daemon
CMD ["./opencortex-server"]

View File

@@ -1,18 +0,0 @@
version: '3.8'
services:
opencortex:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile
container_name: opencortex
restart: unless-stopped
ports:
- "${ORG_AGENT_DAEMON_PORT:-9105}:${ORG_AGENT_DAEMON_PORT:-9105}"
- "${ORG_AGENT_WEB_PORT:-8080}:${ORG_AGENT_WEB_PORT:-8080}"
volumes:
- /memex:/memex
networks:
sandbox-net:
driver: bridge

View File

@@ -1,14 +0,0 @@
;; opencortex: Guix Environment Manifest
;; Usage: guix shell -m manifest.scm -- sbcl --eval ...
(specifications->manifest
'("sbcl"
"sbcl-cl-json"
"sbcl-bordeaux-threads"
"sbcl-usocket"
"sbcl-dexador"
"sbcl-cl-ppcre"
"ripgrep"
"git"
"curl"
"sqlite"))

View File

@@ -1,33 +0,0 @@
#+TITLE: LXC / Systemd-nspawn Deployment Guide
#+AUTHOR: opencortex
* Overview
For users who prefer containerization without the overhead or dependency on the Docker daemon, `opencortex` can be run within a standard Linux Container (LXC) or a systemd-nspawn container.
* Systemd-nspawn Setup (Fastest for Linux users)
1. **Create the container root:**
#+begin_src bash
sudo debootstrap --arch=amd64 bookworm /var/lib/machines/opencortex
#+end_src
2. **Start and enter the container:**
#+begin_src bash
sudo systemd-nspawn -D /var/lib/machines/opencortex
#+end_src
3. **Install dependencies (inside container):**
#+begin_src bash
apt-get update && apt-get install -y sbcl curl git ripgrep libsqlite3-dev build-essential
#+end_src
4. **Bind mount the Memex directory:**
Add this to your container startup or use the `--bind` flag:
#+begin_src bash
sudo systemd-nspawn -D /var/lib/machines/opencortex --bind /home/amr/.openclaw/workspace/memex
#+end_src
* Proxmox LXC Setup
1. Create a new LXC container using the Debian 12 template.
2. Ensure the network is bridged so Emacs can reach it.
3. Run the `deploy/bare-metal/install.sh` script inside the container.

View File

@@ -1,22 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.box = "debian/bookworm64"
config.vm.network "forwarded_port", guest: 9105, host: 9105
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
vb.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y sbcl curl git ripgrep libsqlite3-dev build-essential
# Setup for opencortex
mkdir -p /home/vagrant/opencortex
cp -r /vagrant/* /home/vagrant/opencortex/
chown -R vagrant:vagrant /home/vagrant/opencortex
# Build binary natively
sudo -u vagrant bash -c "cd /home/vagrant/opencortex && ./deploy/bare-metal/install.sh"
SHELL
end

View File

@@ -1,21 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.box = "fedora/39-cloud-base"
config.vm.network "forwarded_port", guest: 9105, host: 9105
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
vb.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
dnf install -y sbcl curl git ripgrep sqlite-devel make gcc
# Setup for opencortex
mkdir -p /home/vagrant/opencortex
cp -r /vagrant/* /home/vagrant/opencortex/
chown -R vagrant:vagrant /home/vagrant/opencortex
# Build binary natively
sudo -u vagrant bash -c "cd /home/vagrant/opencortex && ./deploy/bare-metal/install.sh"
SHELL
end

26
docs/CHANGELOG.org Normal file
View File

@@ -0,0 +1,26 @@
#+TITLE: Changelog
#+STARTUP: content
* v0.1.0 - The Autonomous Foundation (2026-04-20)
This is the initial MVP release of the ~opencortex~. It establishes a secure, auditable Lisp kernel for a personal operating system.
** Features
- **Unified Envelope Architecture:** Actuator-agnostic protocol that decouples routing metadata from cognitive payloads, ensuring all clients (TUI, Emacs, CLI, Matrix) are treated as equal citizens.
- **Metabolic Pipeline:** Robust Perceive-Reason-Act loop with selective memory rollbacks and graceful shutdown handling.
- **Verification Lock:** Mandatory skill enforcement via environment configuration. System halts if security policies or bouncers fail to load.
- **Foveal-Peripheral Context:** High-resolution focus on active tasks with low-resolution skeletal awareness of the rest of the Memex.
- **The Bouncer:** Last-mile deterministic security gate with Deep Packet Inspection for secrets and network exfiltration.
- **Autonomous Scribe:** Background distillation worker that turns daily journal entries into evergreen Zettelkasten notes. Verified to distill atomic concepts autonomously.
- **Autonomous Gardener:** Heartbeat-driven worker that repairs broken links and identifies orphaned nodes in the Memex graph.
- **Unified Onboarding:** Single-command installation (~opencortex.sh~) with Docker support, OS detection, and automated dependency resolution.
- **Channel-Aware TUI:** Interactive Croatoan-based terminal client with clean, human-readable formatting for tool results and system logs.
- **CLI Gateway:** Local TCP socket server for pipe-friendly interaction and frictionless first contact.
** Licensing & Community
- **AGPLv3 License:** OpenCortex is now officially licensed under the GNU Affero General Public License v3.0.
- **Contributor License Agreement:** Implemented a broad CLA (~CLA.org~) for long-term project sustainability.
** Architectural Shift
- Transitioned to **Literate Granularity**: Every function and invariant is now formally documented in its own Org block.
- **Provider Agnosticism:** Implemented a dynamic LLM cascade (OpenRouter, Ollama, etc.) removing all hardcoded backend dependencies.
- **Thin Harness Philosophy:** Decoupled the kernel from specific editors or third-party gateways.

24
docs/CLA.org Normal file
View File

@@ -0,0 +1,24 @@
#+TITLE: OpenCortex Contributor License Agreement (CLA)
#+STARTUP: content
* Overview
This Contributor License Agreement ("Agreement") clarifies the intellectual property license granted with Contributions from any person or entity to the OpenCortex project.
* 1. Definitions
- *"You"* (or *"Your"*) means the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with the Project Lead (Amr Gharbeia).
- *"Contribution"* means any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project Lead for inclusion in the Work.
* 2. Grant of Copyright License
Subject to the terms and conditions of this Agreement, You hereby grant to the Project Lead and to recipients of software distributed by the Project Lead a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
* 3. Grant of Patent License
Subject to the terms and conditions of this Agreement, You hereby grant to the Project Lead and to recipients of software distributed by the Project Lead a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work.
* 4. You Represent That You Are Legally Entitled to Grant the Above License
You represent that each of Your Contributions is Your original creation. You represent that Your Contribution submissions include complete details of any third-party license or other restriction of which You are personally aware and which are associated with any part of Your Contributions.
* 5. Your Rights
You retain all right, title, and interest in and to Your Contributions. This agreement simply grants the Project Lead the necessary rights to distribute the project (including under the AGPLv3 or any future commercial licenses).
* 6. Agreement
By submitting a Pull Request or patch to the OpenCortex repository, You explicitly agree to the terms of this Contributor License Agreement.

44
docs/CONTRIBUTING.org Normal file
View File

@@ -0,0 +1,44 @@
#+TITLE: Contributing to OpenCortex
#+AUTHOR: OpenCortex Contributors
#+STARTUP: content
#+FILETAGS: :docs:contributing:
* Philosophy
OpenCortex is built on a "Zero-Bloat" mandate. The core kernel is mathematically pure, pushing all peripheral logic, API integrations, and routing to hot-reloadable "Skills".
* Literate Granularity
We strictly adhere to Literate Programming using Org-mode.
- *Never* edit `.lisp` files in `src/` directly.
- Modify the corresponding `.org` files in the `literate/` or `skills/` directories.
- Run `org-babel-tangle` to generate the source code.
- Every architectural decision, constraint, and implementation detail must be documented alongside the code in the `.org` file.
* Skill Creation Standard
Skills are the building blocks of OpenCortex. They reside in the `skills/` directory.
A skill must define:
1. *Trigger*: A lambda determining if the skill should activate based on the context.
2. *Probabilistic Gate*: Optional. Generates a prompt for the LLM.
3. *Deterministic Gate*: A hardcoded Lisp function that guarantees safety or executes side-effects (the "Bouncer" pattern).
Example Registration:
#+begin_src lisp
(defskill :skill-example
:priority 100
:trigger (lambda (ctx) ...)
:probabilistic nil
:deterministic (lambda (action ctx) ...))
#+end_src
* The Unified Envelope (Communication Protocol)
All inter-process communication occurs via the Unified Envelope. Do not use legacy specific types like `:CHAT`.
- Always use semantic types: `:REQUEST`, `:EVENT`, `:RESPONSE`, `:STATUS`, `:LOG`.
- Include routing metadata in the `:META` block (e.g., `(:SOURCE :TUI)`).
- Ensure generated `:REQUEST` messages include a mandatory `:TARGET` field.
* Pull Request Process
1. Ensure your working tree is clean.
2. Write tests for your skill in `tests/`.
3. Tangle all files.
4. Run the test suite: `sbcl --eval "(asdf:test-system :opencortex)"`.
5. Submit a PR outlining the architectural intent and the specific Literate changes.

56
docs/USER_MANUAL.org Normal file
View File

@@ -0,0 +1,56 @@
#+TITLE: OpenCortex User Manual
#+AUTHOR: OpenCortex Contributors
#+STARTUP: content
#+FILETAGS: :docs:manual:
* Introduction
Welcome to OpenCortex v0.1.0 (The Autonomous Foundation). OpenCortex is a neurosymbolic AI agent and a Lisp Machine operating system designed to autonomously maintain your Memex (knowledge base) and interact with you via multiple, equal-citizen interfaces.
* Installation
OpenCortex is bootstrapped via a single shell script.
#+begin_src bash
git clone ssh://git@10.10.10.201:2222/amr/opencortex.git
cd opencortex
./opencortex.sh setup
#+end_src
This process will install SBCL, Quicklisp, and prompt you to create a `.env` file for your API keys.
* Configuration
The system is configured via a `.env` file in the project root. Essential variables include:
- `OPENROUTER_API_KEY`: Your LLM provider key.
- `PROVIDER_CASCADE`: The fallback order for LLM providers (e.g., `openrouter,ollama,anthropic`).
- `MEMEX_DIR`: The absolute path to your knowledge base (defaults to `~/memex`).
* Interacting with OpenCortex
Because of the Unified Envelope Architecture, the kernel treats all clients as interchangeable. You must first boot the background daemon:
#+begin_src bash
./opencortex.sh --boot &
#+end_src
** Terminal User Interface (TUI)
For a rich, split-pane terminal experience:
#+begin_src bash
./opencortex.sh tui
#+end_src
** Command Line Interface (CLI)
For raw, pipe-friendly interaction:
#+begin_src bash
./opencortex.sh cli
#+end_src
** Emacs Integration
OpenCortex functions as your "foveal vision" inside Emacs.
1. Ensure `org-agent.el` is loaded.
2. Run `M-x opencortex-connect`.
3. Interact via the `*opencortex-chat*` buffer.
* The Memex Structure
OpenCortex assumes a local folder structure representing your "Memex".
- Core memories and identities are mapped to Org-mode files.
- The `Scribe` background worker distills chronological logs into structured Zettelkasten notes.
- The `Gardener` continuously repairs broken links and flags orphaned nodes.

View File

@@ -1,36 +0,0 @@
#+TITLE: Deployment Guide: Containerized OpenCortex
#+AUTHOR: Amr
#+DATE: [2026-04-11 Sat]
#+FILETAGS: :deployment:docker:infrastructure:
* Overview
The ~opencortex~ is designed to run within a Docker container to ensure system dependencies (SBCL, Quicklisp, signal-cli) are perfectly matched across different host environments.
* Prerequisites
- Docker Engine
- Docker Compose
- A valid ~.env~ file in the ~projects/opencortex/~ directory (refer to ~.env.example~).
* Quick Start
** 1. Build and Start
From the ~projects/opencortex/~ directory:
#+begin_src bash
docker-compose up --build -d
#+end_src
** 2. Check Logs
#+begin_src bash
docker-compose logs -f
#+end_src
* Volume Mapping
The ~docker-compose.yml~ file automatically mounts your host's ~memex~ directory to ~/memex~ inside the container. This allows the agent to:
1. Read/Write to your Zettelkasten and GTD files.
2. Maintain its local state (Memory, snapshots).
* Troubleshooting
** signal-cli Identity
If using the Signal gateway, ensure you have registered your number via the host's ~signal-cli~ or within the container. The state is preserved in the ~signal-state~ Docker volume.
** Re-loading Skills
The container pre-caches dependencies during the build. If you modify core Lisp logic, you must rebuild the image (~--build~). If you only modify ~.org~ skills in your memex, the agent can reload them dynamically if they are part of the startup scan.

View File

@@ -1,46 +0,0 @@
#+TITLE: v0.1.0 Launch & Marketing Plan
#+AUTHOR: Amr
#+FILETAGS: :marketing:release:autonomy:
#+STARTUP: content
* Overview
With the v0.1.0 "Autonomous MVP" released, the goal is to leverage GitHub's social graph to build a community of early adopters, contributors, and power users who resonate with the "Thin Harness, Fat Skills" and "Local-First" philosophy.
* 1. Licensing Strategy
Before wide promotion, the project's license must align with its goals.
- **MIT License (Current):** Maximum adoption, frictionless for developers to embed in their own tools. Good for rapid growth.
- **GPLv3 / AGPLv3:** Enforces copyleft. Ensures any modifications or integrations by corporations must remain open-source. Protects the "Autonomous" ethos from proprietary enclosure.
- **Dual Licensing:** Open-source for individuals, commercial license for enterprise usage (if monetization is a future goal).
*Decision Needed:* Do we stick with MIT, or switch to a copyleft license (AGPL) to protect the autonomous nature of the project?
* 2. The GitHub Migration & Setup
To maximize visibility, the repository must be optimized for GitHub's ecosystem.
- [ ] **Mirror/Migrate to GitHub:** Move the primary remote from the self-hosted Gitea to GitHub.
- [ ] **README Optimization:** Add badges (License, Build Status, Version). Ensure the "Zero-to-One" curl command is prominent. Add an architecture diagram (mermaid).
- [ ] **Repository Topics:** Add tags like `common-lisp`, `autonomous-agents`, `org-mode`, `pkm`, `zettelkasten`, `llm`, `local-first`.
- [ ] **Contributing Guide:** Add `CONTRIBUTING.md` to explain the Literate Programming standard and how to add new "Skills".
- [ ] **Issue Templates:** Create templates for "Bug Report" and "Skill Proposal".
* 3. The PR & Social Media Campaign
The narrative: "An autonomous AI agent that doesn't just chat, but lives natively in your Org-mode Memex. No Python glue code, no cloud lock-in—just pure, homoiconic Common Lisp."
** Target Audiences & Channels
1. **The Emacs / Org-mode Community:**
- *Channels:* `r/emacs`, `r/orgmode`, Hacker News (`/r/lisp`), Emacs News.
- *Hook:* "A background daemon that autonomously distills your daily logs into a Zettelkasten using LLMs."
2. **The Local-First / PKM Community:**
- *Channels:* `r/Zettelkasten`, `r/PKM`, Obsidian/Logseq diaspora looking for more power.
- *Hook:* "Own your brain. An AI agent that runs locally on your Markdown/Org files with mathematical security gates."
3. **The AI / Autonomous Agent Hackers:**
- *Channels:* Hacker News (Show HN), Twitter/X (AI tech Twitter).
- *Hook:* "Tired of fragile Python/Playwright agent wrappers? opencortex uses a deterministic Lisp microkernel to formally verify LLM actions before execution."
** Launch Materials
- **Demo Video (2 minutes):** Show the one-liner install, the agent running the `Scribe` skill in the background, and the user querying it via `opencortex chat`.
- **Blog Post / Essay:** "Why we built an Autonomous Agent in Common Lisp." Discuss the fragility of current SOTA (Devin/SWE-agent) and the necessity of the Bouncer/Policy gates.
* 4. Post-Launch Community Engagement
- Encourage "Show and Tell" in GitHub Discussions.
- Create a "Skill Directory" where users can share their custom `.org` skills.
- Actively solicit feedback for the v0.2.0 (Lisp TUI) roadmap.

View File

@@ -1,55 +0,0 @@
#+TITLE: Quickstart Guide: The Road to Autonomousty
#+AUTHOR: Amr
#+DATE: [2026-04-11 Sat]
#+FILETAGS: :quickstart:onboarding:guide:
* 1. Introduction
Welcome to ~opencortex~, the "Executive Soul" of your personal OS. This guide will help you set up and interact with your first probabilistic-deterministic agent.
* 2. Prerequisites
Before launching the harness, ensure your host environment has:
- **Docker & Docker Compose**: The primary enclosure for the Lisp Machine.
- **LLM API Keys**: At least one key for Gemini, Anthropic, or OpenAI.
- **Emacs (Optional)**: For the full literate experience via ~opencortex.el~.
* 3. Installation & Enclosure
** Step 1: Clone the Autonomousty
#+begin_src bash
git clone https://github.com/amr/opencortex.git
cd opencortex
#+end_src
** Step 2: Secret Configuration
Copy the example environment file and add your keys.
#+begin_src bash
cp .env.example .env
# Edit .env with your favorite editor
#+end_src
** Step 3: Launch the Image
This will build the SBCL environment and start the Micro-Loader.
#+begin_src bash
docker-compose up --build -d
#+end_src
* 4. Interaction Gateways
Once the harness is "Ready", you can interact with it via multiple sensors.
** Gateway A: Emacs (communication protocol)
If you have configured the ~opencortex~ package in Emacs:
1. Open a chat buffer: ~M-x opencortex-chat-open~.
2. Send: "Are you online, agent?"
** Gateway B: External Sensors
If you enabled Signal or Telegram in ~.env~, send a message directly to your bot.
* 5. Verification (The Chaos Check)
To ensure the harness is fully healthy, check the logs for the Micro-Loader summary:
#+begin_src bash
docker-compose logs -f opencortex
#+end_src
Look for: ~LOADER: Boot Complete. [Ready: 34] [Failed: 0]~
* 6. Next Steps
- **Extend the Brain**: Read the [[file:skill-creation.org][Skill Creation Guide]] to add custom Lisp skills.
- **Deep Dive**: Explore the [[file:../literate/][literate/]] directory to understand the harness's architecture.

View File

@@ -1,66 +0,0 @@
#+TITLE: User Experience (UX) Journey
#+AUTHOR: Amr
#+FILETAGS: :ux:design:autonomy:
#+STARTUP: content
* Overview
This document traces the intended User Experience (UX) journey for the ~opencortex~. It serves as a living design document to ensure that architectural decisions align with a frictionless, autonomous, and intuitive user interaction model.
* 1. The Zero-to-One Experience (Onboarding)
** Goal
A user should be able to go from discovering the project to having a running, calibrated agent in under 3 minutes, with zero prerequisite knowledge of Lisp.
** The Appliance Paradigm (Primary Path)
The user runs a single command in their terminal:
#+begin_src bash
curl -fsSL https://raw.githubusercontent.com/gharbeia/opencortex/main/scripts/install.sh | bash
#+end_src
** The Interactive Wizard
The script verifies Docker presence and then launches an interactive prompt before booting the container:
1. *Identity:* "What is your name?" -> Configures ~$MEMEX_USER~
2. *Assistant:* "What shall we name your Assistant?" -> Configures ~$MEMEX_ASSISTANT~
3. *Neural Provider:* "Select your primary neural provider [Gemini/OpenRouter/Anthropic/OpenAI]" -> Configures API Keys.
4. *Data Gravity:* "Where is your Memex located?" -> Maps the host directory to the Docker container.
*Outcome:* The `.env` is generated, core skills are seeded into the user's Memex, and `docker-compose up -d` launches the daemon in the background. The user sees: /"Booting your autonomous brain in the background..."/
* 2. The First Contact (The CLI Gateway)
** Goal
Immediately after boot, the user needs a way to verify the agent is alive and capable of answering questions about their Memex without configuring complex third-party integrations (like Telegram bots).
** The Interaction
The user types a local client command to connect to the background daemon:
#+begin_src bash
opencortex chat
#+end_src
This opens a slick, colorful interactive terminal session:
#+begin_example
> User: Hello, what are my active projects?
> Agent: [Thinking...]
> Agent: You currently have 3 active projects:
> 1. OpenCortex v1.0
> 2. Home Renovation
> 3. Read 'The Autonomous Individual'
#+end_example
** Behind the Scenes
1. The ~opencortex chat~ client connects to the daemon's local port (e.g., 9105).
2. It sends a ~:chat-message~ signal.
3. The core harness routes this to the Probabilistic Engine.
4. The Context Manager retrieves active projects from the Memex AST.
5. The Deterministic Engine (Bouncer) verifies it is a safe read-only action.
6. The ~:cli~ Actuator formats the Lisp response into Markdown and sends it back over the socket.
* 3. The Interactive Refinement (v0.2.0)
** Goal
Transition from a "Verified Wrapper" around netcat to a high-fidelity, native Common Lisp TUI that rivals the experience of ~gemini-cli~.
** Features
- *Homoiconic UI:* The TUI is rendered directly by the Lisp kernel, allowing for live introspection of the agent's thoughts.
- *Rich Formatting:* ANSI colors, bold headers, and syntax-highlighted code blocks.
- *Command Palette:* Slash commands for system control without leaving the chat.
* 4. The Continuous Loop (Daily Usage)
(To be defined as the agent's capabilities expand into Scribe, Gardener, and Emacs-native interactions).

388
harness/act.org Normal file
View File

@@ -0,0 +1,388 @@
#+TITLE: Stage 3: Act (act.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:act:
#+STARTUP: content
* Stage 3: Act (act.lisp)
** Architectural Intent: The Last Mile
The Act stage is where cognition meets reality. After the Probabilistic engine proposes and the Deterministic engine verifies, Act executes the approved action.
The key insight of the Act stage is that *execution is the point of no return*. Once a command is sent to the shell or a file is written, side effects have occurred. Therefore, Act implements a "last-mile" safety check - even after skills have verified the action, there's a final validation before dispatch.
** Why Separate Actuators?
The actuator pattern decouples /what to do/ from /how to do it/:
- The reasoning engine generates action plists like `(:TYPE :REQUEST :TARGET :SHELL :PAYLOAD ...)`
- The actuator interprets the target and executes appropriately
- Adding a new actuator (Telegram, Matrix, etc.) doesn't require changing the reasoning code
This follows the Open/Closed principle: open for extension, closed for modification.
** The Feedback Loop
Act is unique in the pipeline because it can generate new signals. When a tool executes and returns data, that data becomes a new signal that feeds back into Perceive → Reason → Act.
Example feedback chain:
1. User asks "What files changed today?"
2. Reason generates shell command action
3. Act executes shell, gets file list
4. Act returns file list as feedback signal
5. Reason processes file list, generates human-readable response
6. Act displays response
* Package Context
#+begin_src lisp :tangle ../library/act.lisp
(in-package :opencortex)
#+end_src
* Actuator Configuration
** Actuator Registry Variables
#+begin_src lisp :tangle ../library/act.lisp
(defvar *default-actuator* :cli
"The actuator used when no explicit target is specified.
Override with DEFAULT_ACTUATOR environment variable.")
(defvar *silent-actuators* '(:cli :system-message :emacs)
"List of actuators that don't generate tool-output feedback.
These typically have their own feedback mechanisms (CLI prints directly, etc.)")
#+end_src
** initialize-actuators: System Bootstrap
#+begin_src lisp :tangle ../library/act.lisp
(defun initialize-actuators ()
"Load actuator configuration from environment and register core actuators.
Environment variables:
- DEFAULT_ACTUATOR: Keyword for default target (:cli, :shell, etc.)
- SILENT_ACTUATORS: Comma-separated list of actuators that skip feedback
Registers three core actuators:
1. :system - Internal commands (eval, create-skill, message)
2. :tool - Cognitive tool execution
3. :tui - Terminal UI output via reply stream"
;; Load environment configuration
(let ((def (uiop:getenv "DEFAULT_ACTUATOR"))
(silent (uiop:getenv "SILENT_ACTUATORS")))
;; Set default actuator
(when def
(setf *default-actuator*
(intern (string-upcase def) "KEYWORD")))
;; Parse silent actuators list
(when silent
(setf *silent-actuators*
(mapcar (lambda (s)
(intern (string-upcase (string-trim '(#\Space) s))
"KEYWORD"))
(str:split "," silent)))))
;; Register core harness actuators
(register-actuator :system #'execute-system-action)
(register-actuator :tool #'execute-tool-action)
;; TUI actuator: sends response back through the reply stream
(register-actuator :tui (lambda (action context)
(let* ((meta (getf context :meta))
(stream (getf meta :reply-stream)))
(when (and stream (open-stream-p stream))
(format stream "~a" (frame-message action))
(finish-output stream))))))
#+end_src
* Action Dispatching
** dispatch-action: The Router
#+begin_src lisp :tangle ../library/act.lisp
(defun dispatch-action (action context)
"Route an approved action to its registered actuator.
ACTION is a plist with structure:
(:TYPE :REQUEST :TARGET :shell :PAYLOAD (...))
CONTEXT is the signal being processed (for metadata access)
The target is resolved in order of priority:
1. Explicit :target in the action
2. :source from the original signal's metadata
3. *default-actuator* configuration variable
Returns the actuator's result (may be a feedback signal or NIL)."
(let ((payload (proto-get action :payload)))
;; Heartbeats don't generate actuation
(when (eq (proto-get payload :sensor) :heartbeat)
(return-from dispatch-action nil))
(when (and action (listp action))
(let* ((meta (proto-get context :meta))
(source (proto-get meta :source))
(raw-target (or (ignore-errors (getf action :TARGET))
(ignore-errors (getf action :target))
source
*default-actuator*))
(target (intern (string-upcase (string raw-target)) :keyword))
(actuator-fn (gethash target *actuator-registry*)))
;; Preserve metadata in outbound action
(when (and meta (null (getf action :meta)))
(setf (getf action :meta) meta))
;; Execute or log error
(if actuator-fn
(funcall actuator-fn action context)
(harness-log "ACT ERROR: No actuator registered for '~s' (requested by ~s)"
target raw-target))))))
#+end_src
* Actuator Implementations
** execute-system-action: Internal Commands
#+begin_src lisp :tangle ../library/act.lisp
(defun execute-system-action (action context)
"Execute internal harness commands.
This actuator handles meta-commands that affect the harness itself,
rather than external side effects. Commands include:
- :eval - Evaluate arbitrary Lisp code (DANGEROUS, validate first!)
- :create-skill - Write a new skill org file and reload
- :message - Log a message to the harness log
These commands bypass the normal actuator system since they operate
on the harness internals rather than external systems."
(declare (ignore context))
(let* ((payload (ignore-errors (getf action :payload)))
(cmd (ignore-errors (getf payload :action))))
(case cmd
;; Evaluate Lisp code - guarded by lisp-validator skill
(:eval
(let ((code (getf payload :code)))
(eval (read-from-string code))))
;; Create and load a new skill from content
(:create-skill
(let* ((filename (getf payload :filename))
(content (getf payload :content))
(skills-dir (merge-pathnames "skills/"
(asdf:system-source-directory :opencortex)))
(full-path (merge-pathnames filename skills-dir)))
(with-open-file (out full-path
:direction :output
:if-exists :supersede)
(write-string content out))
(load-skill-from-org full-path)))
;; Log an informational message
(:message
(harness-log "ACT [System]: ~a" (getf payload :text)))
;; Unknown command
(t
(harness-log "ACT ERROR [System]: Unknown command '~s'" cmd)))))
#+end_src
** execute-tool-action: Cognitive Tool Execution
#+begin_src lisp :tangle ../library/act.lisp
(defun execute-tool-action (action context)
"Execute a registered cognitive tool.
Tools are registered functions with:
- A guard function (optional, for safety checks)
- A body function (the actual implementation)
- Metadata (description, parameter specs)
This actuator:
1. Looks up the tool by name
2. Runs the guard function (if present)
3. Executes the body function with parsed arguments
4. Returns a feedback signal with the result
The feedback mechanism allows tool results to trigger further reasoning."
(let* ((payload (getf action :payload))
(tool-name (getf payload :tool))
(tool-args (getf payload :args))
(depth (getf context :depth 0))
(meta (getf context :meta))
(source (getf meta :source))
(tool (gethash (string-downcase (string tool-name)) *cognitive-tools*)))
(if tool
(handler-case
;; Parse arguments (handle both flat and nested plists)
(let* ((clean-args (if (and (listp tool-args)
(listp (car tool-args)))
(car tool-args)
tool-args))
(result (funcall (cognitive-tool-body tool) clean-args)))
;; Format result for source
(when source
(dispatch-action (list :TYPE :REQUEST
:TARGET source
:PAYLOAD (list :ACTION :MESSAGE
:TEXT (format-tool-result tool-name result)))
context))
;; Return feedback signal for potential further processing
(list :TYPE :EVENT
:DEPTH (1+ depth)
:META meta
:PAYLOAD (list :SENSOR :tool-output
:RESULT result
:TOOL tool-name)))
;; Tool execution error
(error (c)
(list :TYPE :EVENT
:DEPTH (1+ depth)
:META meta
:PAYLOAD (list :SENSOR :tool-error
:TOOL tool-name
:MESSAGE (format nil "~a" c)))))
;; Tool not found
(list :TYPE :EVENT
:DEPTH (1+ depth)
:META meta
:PAYLOAD (list :SENSOR :tool-error
:MESSAGE (format nil "Tool '~a' not found" tool-name)))))
#+end_src
** format-tool-result: Human-Readable Output
#+begin_src lisp :tangle ../library/act.lisp
(defun format-tool-result (tool-name result)
"Format a tool result for human-readable display.
Tools return either:
- A plist: (:status :success :content \"...\") or (:status :error :message \"...\")
- A raw value (string, number, etc.)
This function normalizes both formats into a consistent string presentation."
(if (listp result)
(let ((status (getf result :status))
(content (getf result :content))
(msg (getf result :message)))
(cond
((and (eq status :success) content)
(format nil "~a" content))
((and (eq status :error) msg)
(format nil "ERROR [~a]: ~a" tool-name msg))
(t
(format nil "TOOL [~a] RESULT: ~s" tool-name result))))
(format nil "TOOL [~a] RESULT: ~a" tool-name result)))
#+end_src
* The Act Gate
** act-gate: Final Pipeline Stage
#+begin_src lisp :tangle ../library/act.lisp
(defun act-gate (signal)
"Final stage of the metabolic pipeline: Actuation.
This stage has three responsibilities:
1. Last-mile safety check: Run deterministic gates one more time
before execution (handles race conditions, concurrent modifications)
2. Actuation: Dispatch the approved action to its target actuator
3. Feedback generation: If the action produced results, create a
feedback signal that feeds back into the pipeline
Modifies the signal:
- :approved-action - May be modified by last-mile verification
- :status - Set to :acted
Returns a feedback signal if the action produced results, otherwise NIL."
(let* ((approved (getf signal :approved-action))
(type (getf signal :type))
(meta (getf signal :meta))
(source (getf meta :source))
(feedback nil)
(context signal))
;; Step 1: Last-mile deterministic verification
;; This catches any issues that arose between reasoning and acting
(when approved
(let* ((original-type (getf approved :type))
(verified (deterministic-verify approved signal)))
;; Check if deterministic verification blocked the action
(if (and (listp verified)
(member (getf verified :type) '(:LOG :EVENT :log :event))
(not (member original-type '(:LOG :EVENT :log :event))))
;; Action was blocked by verification
(progn
(harness-log "ACT BLOCKED: Action failed last-mile deterministic check.")
(setf (getf signal :approved-action) nil)
(setf approved nil)
(setf feedback verified))
;; Action passed verification
(progn
(setf (getf signal :approved-action) verified)
(setf approved verified)))))
;; Step 2: Actuation based on signal type
(case type
;; Explicit requests go directly to dispatch
(:REQUEST
(dispatch-action signal context))
;; Log messages also dispatch
(:LOG
(dispatch-action signal context))
;; Events with approved actions dispatch to their target
(:EVENT
(if approved
(let* ((target (getf approved :target))
(result (dispatch-action approved context)))
;; Determine feedback based on actuator response
(cond
;; Actuator returned a signal - use it as feedback
((and (listp result)
(member (getf result :type) '(:EVENT :LOG)))
(setf feedback result))
;; Non-silent actuator with result - format as tool-output
((and result
(not (member target *silent-actuators*)))
(setf feedback (list :type :EVENT
:depth (1+ (getf signal :depth 0))
:meta meta
:payload (list :sensor :tool-output
:result result
:tool approved))))))
;; No approved action, but have source - might be raw event
(when source
(dispatch-action signal context)))))
;; Step 3: Update signal status
(setf (getf signal :status) :acted)
feedback))
#+end_src

155
harness/communication.org Normal file
View File

@@ -0,0 +1,155 @@
#+TITLE: Communication Protocol (communication.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:protocol:
#+STARTUP: content
* Communication Protocol (communication.lisp)
** Architectural Intent: Secure Inter-Process Communication & Deterministic Framing
The ~communication.lisp~ module defines the low-level transport and framing logic for OpenCortex stimuli.
* Implementation (communication.lisp)
#+begin_src lisp :tangle ../library/package.lisp
(in-package :opencortex)
(defun proto-get (plist key)
"Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions."
(let* ((s (string key))
(up (intern (string-upcase s) :keyword))
(dn (intern (string-downcase s) :keyword)))
(or (getf plist up) (getf plist dn))))
#+end_src
#+begin_src lisp :tangle ../library/communication.lisp
(in-package :opencortex)
(defvar *actuator-registry* (make-hash-table :test 'equalp)
"Global registry mapping target keywords to their physical actuator functions.")
(defun register-actuator (name fn)
"Registers an actuator function. Actuators receive: (ACTION CONTEXT)."
(let ((key (if (keywordp name) name (intern (string-upcase (string name)) :keyword))))
(setf (gethash key *actuator-registry*) fn)))
(defun frame-message (msg-plist)
"Frames a Lisp plist with a 6-character hex length and a newline for stream integrity."
(let* ((*print-pretty* nil)
(*print-circle* nil)
(msg-string (format nil "~s" msg-plist))
(len (length msg-string)))
(format nil "~6,'0x~a~%" len msg-string)))
(defun read-framed-message (stream)
"Reads a hex-length prefixed S-expression from the stream securely. Skips leading whitespace."
(let ((length-buffer (make-string 6)))
(handler-case
(progn
;; 1. Skip leading whitespace (newlines, spaces, etc.)
(loop for char = (peek-char nil stream nil :eof)
while (and (not (eq char :eof)) (member char '(#\Space #\Newline #\Tab #\Return)))
do (read-char stream))
;; 2. Read the 6-char hex length
(let ((count (read-sequence length-buffer stream)))
(cond ((< count 6) :eof)
(t (let ((len (ignore-errors (parse-integer length-buffer :radix 16))))
(if (not len)
(progn
(harness-log "PROTOCOL ERROR: Invalid header ~s. Attempting resync..." length-buffer)
:error)
(let ((msg-buffer (make-string len)))
(read-sequence msg-buffer stream)
(let ((*read-eval* nil)
(*print-pretty* nil))
(handler-case
(let ((msg (read-from-string msg-buffer)))
(validate-communication-protocol-schema msg)
msg)
(error (c)
(harness-log "PROTOCOL PARSE ERROR: ~a in ~s" c msg-buffer)
:error))))))))))
(error (c)
(harness-log "PROTOCOL READ ERROR: ~a" c)
:error))))
(defun make-hello-message (version)
"Constructs the standard HELLO handshake message."
(list :TYPE :EVENT
:PAYLOAD (list :ACTION :handshake
:VERSION version
:CAPABILITIES '(:AUTH :SWANK :ORG-AST))))
#+end_src
** Structural Validation (communication-validator.lisp)
The validator ensures that incoming messages adhere to the strict property list schema of the communication protocol.
#+begin_src lisp :tangle ../library/communication-validator.lisp
(in-package :opencortex)
(defun validate-communication-protocol-schema (msg)
"Strict structural validation for incoming communication protocol messages."
(unless (listp msg)
(error "Communication Protocol Schema Error: Message must be a property list (got ~s)" (type-of msg)))
(let ((type (let ((raw (proto-get msg :type))) (if (keywordp raw) (intern (string-upcase (string raw)) :keyword) raw))))
(unless (member type '(:REQUEST :EVENT :RESPONSE :LOG :STATUS))
(progn (harness-log "REJECTED MSG: ~s" msg) (error "Communication Protocol Schema Error: Invalid message type '~a'" type)))
(case type
(:REQUEST
;; Allow missing :target if :source is present in :meta, since reason-gate
;; will infer :target from :source downstream. This preserves "equality of
;; clients" — gateways need not duplicate routing logic.
(let ((target (proto-get msg :target))
(source (proto-get (proto-get msg :meta) :source)))
(unless (or target source)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :target and no :source in :meta to infer it"))
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :payload"))))
(:EVENT
(let ((payload (proto-get msg :payload)))
(unless (and payload (listp payload))
(error "Communication Protocol Schema Error: EVENT missing or invalid :payload"))
(unless (or (proto-get payload :action) (proto-get payload :sensor))
(error "Communication Protocol Schema Error: EVENT payload must contain :action or :sensor"))))
(:RESPONSE
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: RESPONSE missing mandatory :payload"))))
t))
(defskill :skill-communication-protocol-validator
:priority 95
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:protocol-received)))
:probabilistic nil
:deterministic (lambda (action ctx)
(declare (ignore ctx))
(validate-communication-protocol-schema action)
action))
#+end_src
** Message Framing (communication.lisp)
Frames a message with a hex length prefix and ensures all data is serializable.
#+begin_src lisp :tangle ../library/communication.lisp
(defun sanitize-protocol-message (msg)
"Recursively strips non-serializable objects from a protocol plist."
(if (and msg (listp msg))
(let ((clean nil))
(loop for (k v) on msg by #'cddr
do (unless (member k '(:reply-stream :socket :stream))
(push k clean)
(push (if (listp v) (sanitize-protocol-message v) v) clean)))
(nreverse clean))
msg))
(defun frame-message (msg)
"Serializes a message plist and prefixes it with a 6-character hex length."
(let* ((sanitized (sanitize-protocol-message msg))
(payload (let ((*print-pretty* nil) (*read-eval* nil)) (format nil "~s" sanitized)))
(len (length payload)))
(format nil "~6,'0x~a" len payload)))
#+end_src

View File

@@ -37,14 +37,14 @@ The ~context.lisp~ module provides the deterministic functional layer for queryi
** Package Context
We begin by ensuring we are executing within the correct isolated package namespace.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(in-package :opencortex)
#+end_src
** Querying the Store (context-query-store)
A generalized filter for the Memory. This function allows skills to perform high-level semantic sweeps of the Memex based on tags, TODO states, or Org element types. It returns a list of ~org-object~ structures.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-query-store (&key tag todo-state type)
"Filters the Memory based on tags, todo states, or types."
(let ((results nil))
@@ -62,7 +62,7 @@ A generalized filter for the Memory. This function allows skills to perform high
** Active Projects (context-get-active-projects)
Identifies headlines tagged with ~project~ that have not yet reached a terminal ~DONE~ state. This provides the primary high-level structure for the agent's global awareness.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-get-active-projects ()
"Returns headlines tagged as 'project' that are not yet marked DONE."
(remove-if (lambda (obj) (equal (getf (org-object-attributes obj) :TODO-STATE) "DONE"))
@@ -72,7 +72,7 @@ Identifies headlines tagged with ~project~ that have not yet reached a terminal
** Completed Tasks (context-get-recent-completed-tasks)
Retrieves a list of tasks that have reached the terminal ~DONE~ state. This is useful for providing the agent with historical context or for generating summaries of recent work.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-get-recent-completed-tasks ()
"Retrieves recently finished tasks from the store."
(context-query-store :todo-state "DONE" :type :HEADLINE))
@@ -81,7 +81,7 @@ Retrieves a list of tasks that have reached the terminal ~DONE~ state. This is u
** Capability Discovery (context-list-all-skills)
Provides a sorted list of all currently loaded skills. In a "Self-Writing" environment, the agent must be able to discover and understand its own capabilities. This function provides the metadata necessary for the agent to decide which skill to trigger or how to resolve dependencies.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-list-all-skills ()
"Provides a sorted overview of currently loaded system capabilities."
(let ((results nil))
@@ -95,7 +95,7 @@ Provides a sorted list of all currently loaded skills. In a "Self-Writing" envir
** Skill Inspection (context-get-skill-source)
Reads the raw literate Org source of a specific skill. This is a foundational capability for an agent expected to eventually "self-write" or perform its own maintenance. By reading the literate source, the agent can understand the *intent* behind a skill's logic before proposing a modification. We use the `SKILLS_DIR` environment variable to locate the source files.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-get-skill-source (skill-name)
"Reads the raw literate source of a specific skill for inspection."
(let* ((filename (format nil "~a.org" skill-name))
@@ -108,7 +108,7 @@ Reads the raw literate Org source of a specific skill. This is a foundational ca
** Harness Logs (context-get-system-logs)
Retrieves the most recent entries from the harness's internal circular log buffer. This allows the Probabilistic Engine to see recent errors or successful dispatches, enabling it to course-correct or explain failures to the user. The log limit is externalized to `CONTEXT_LOG_LIMIT`.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-get-system-logs (&optional limit)
"Retrieves the most recent lines from the harness's internal log."
(let ((log-limit (or limit (ignore-errors (parse-integer (uiop:getenv "CONTEXT_LOG_LIMIT"))) 20)))
@@ -128,7 +128,7 @@ It implements the following deterministic logic:
The semantic threshold is externalized to `CONTEXT_SEMANTIC_THRESHOLD`.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-render-to-org (obj &key (depth 1) (foveal-id nil) semantic-threshold (foveal-vector nil))
"Recursively renders an org-object and its children to an Org string using a Foveal-Peripheral Hybrid model."
(let* ((id (org-object-id obj))
@@ -177,23 +177,26 @@ The semantic threshold is externalized to `CONTEXT_SEMANTIC_THRESHOLD`.
** Path Resolution (context-resolve-path)
A utility function that expands environment variables (like ~$HOME~ or ~$MEMEX_ROOT~) within path strings. This ensures that the agent can interact with files across different machine configurations without hardcoding absolute paths. This version is more robust, supporting multiple environment variables throughout the string.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-resolve-path (path-string)
"Expands all environment variables ($VAR) within a path string."
(if (and (stringp path-string) (search "$" path-string))
(let ((result path-string))
(ppcre:do-register-groups (var-name) ("\\$([A-Za-z0-9_]+)" path-string)
(let ((var-val (uiop:getenv var-name)))
(when var-val
(setf result (ppcre:regex-replace (format nil "\\$~a" var-name) result var-val)))))
result)
path-string))
"Expands environment variables and strips literal quotes from a path string."
(let ((path (if (stringp path-string)
(string-trim '(#\" #\' #\Space) path-string)
path-string)))
(if (and (stringp path) (search "$" path))
(let ((result path))
(ppcre:do-register-groups (var-name) ("\\$([A-Za-z0-9_]+)" path)
(let ((var-val (uiop:getenv var-name)))
(when var-val
(setf result (ppcre:regex-replace (format nil "\\$~a" var-name) result var-val)))))
result)
path)))
#+end_src
** Global Awareness (context-assemble-global-awareness)
The primary entry point for context generation. This function identifies active projects and the current user focus (captured during the Perceive stage), then invokes the recursive renderer to assemble the pruned Org-mode skeletal outline sent to the LLM.
#+begin_src lisp :tangle ../src/context.lisp
#+begin_src lisp :tangle ../library/context.lisp
(defun context-assemble-global-awareness (&optional signal)
"Produces a high-level skeletal outline of the current Memory for the LLM."
(let* ((foveal-id (or (getf signal :foveal-focus)

287
harness/loop.org Normal file
View File

@@ -0,0 +1,287 @@
#+TITLE: The Metabolic Loop (loop.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:loop:
#+STARTUP: content
* The Metabolic Loop (loop.lisp)
** Architectural Intent
The Metabolic Loop is the /cranial nerve reflex/ of OpenCortex. While skills provide specialized intelligence, the loop provides the fundamental rhythm of existence: the continuous processing of signals from perception through cognition to action.
Unlike a simple event loop, the Metabolic Loop implements a sophisticated error recovery model. When the system encounters an error, it distinguishes between:
1. *Transient errors* (tool failures, network timeouts) - recoverable, no state rollback
2. *Critical errors* (undefined functions, malformed data structures) - require memory rollback
3. *Recursive loops* (signals generating more signals indefinitely) - depth limit enforcement
This design ensures the agent remains stable under adverse conditions while preserving the ability to recover from genuine system failures.
** Why Separate Perceive-Reason-Act?
The three-stage pipeline mirrors the classical sense-think-act paradigm but with a crucial difference: each stage is a pure function that transforms a signal. This allows:
- *Perceive* to normalize raw input into a standardized signal format
- *Reason* to transform the perceived signal into an approved action (or reject it)
- *Act* to execute the approved action and potentially generate a feedback signal
The feedback loop (Act returning a signal that feeds back into Perceive) enables complex multi-step operations where each action can trigger subsequent reasoning.
** Thread Safety
The loop operates in a multi-threaded environment:
- The main thread runs the heartbeat and idle loop
- Async sensors spawn threads for non-blocking I/O
- Interrupt handling requires mutex protection to prevent race conditions
* Package and Thread-Safe Variables
#+begin_src lisp :tangle ../library/loop.lisp
(in-package :opencortex)
(defvar *interrupt-flag* nil
"Atomic flag set by signal handlers to trigger graceful shutdown.
Using a dedicated variable avoids race conditions in interrupt handling.")
(defvar *interrupt-lock* (bt:make-lock "harness-interrupt-lock")
"Mutex protecting *interrupt-flag* access.
Locking is required because SBCL's interrupt handlers run in uncertain contexts.")
(defvar *heartbeat-thread* nil
"Handle to the heartbeat thread, allowing explicit termination on shutdown.")
#+end_src
* The Metabolic Pipeline
** process-signal: The Core Engine
This function implements the Perceive-Reason-Act pipeline. It processes a signal through all three stages and handles the feedback loop where Actions can generate new signals.
The depth counter prevents infinite recursion—a signal that generates another signal that generates another, etc. By limiting to depth 10, we ensure the system eventually converges or gracefully terminates.
#+begin_src lisp :tangle ../library/loop.lisp
(defun process-signal (signal)
"The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act.
SIGNAL is a property list with the following structure:
- :type - :EVENT, :REQUEST, :RESPONSE, etc.
- :payload - The actual content (sensor data, approved actions, etc.)
- :meta - Metadata including source, session, reply stream
- :depth - Recursion depth counter (starts at 0)
- :status - Processing status (:perceived, :reasoned, :acted)
Returns NIL when processing is complete, or a new signal for feedback loop."
(let ((current-signal signal))
(loop while current-signal do
;; Depth limiting prevents infinite recursion from feedback loops
(let ((depth (getf current-signal :depth 0))
(meta (getf current-signal :meta)))
(when (> depth 10)
(harness-log "METABOLISM ERROR: Max recursion depth reached.")
(return nil))
;; Check for graceful shutdown interrupt
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
(harness-log "METABOLISM: Interrupted by shutdown signal.")
(bt:with-lock-held (*interrupt-lock*) (setf *interrupt-flag* nil))
(return nil))
;; The three-stage pipeline wrapped in error handling
(handler-case
(progn
;; Stage 1: Perceive - normalize sensory input
(setf current-signal (perceive-gate current-signal))
;; Stage 2: Reason - generate and verify action proposals
(setf current-signal (reason-gate current-signal))
;; Stage 3: Act - execute approved actions
(let ((feedback (act-gate current-signal)))
(if feedback
;; Action generated a feedback signal - continue processing
(progn
;; Preserve metadata from original signal
(unless (getf feedback :meta)
(setf (getf feedback :meta) meta))
(setf current-signal feedback))
;; No feedback - pipeline complete
(setf current-signal nil))))
;; Error recovery with differentiated response
(error (c)
(let ((sensor (ignore-errors (getf (getf current-signal :payload) :sensor))))
(harness-log "METABOLISM CRASH [~a]: ~a" (or sensor :unknown) c)
;; Only rollback memory on critical errors, not transient tool failures
;; This prevents losing recent context due to a single bad API call
(unless (member sensor '(:loop-error :tool-error :syntax-error))
(harness-log "CRITICAL ERROR: Initiating Micro-Rollback.")
(rollback-memory 0))
;; At deep recursion or known error types, terminate gracefully
(if (or (> depth 2) (member sensor '(:loop-error :tool-error)))
(setf current-signal nil)
;; Otherwise, convert error to a loop-error signal for retry
(setf current-signal
(list :type :EVENT
:depth (1+ depth)
:meta meta
:payload (list :sensor :loop-error
:message (format nil "~a" c)
:depth depth)))))))))))
#+end_src
** The Feedback Loop Explained
The pipeline implements a feedback loop where Act can return a new signal:
1. User input arrives → Perceive normalizes it
2. Reason generates an action → Act executes it
3. If the action was a tool call that returned new information → Act returns a feedback signal
4. Feedback signal feeds back into step 1 for further reasoning
This enables multi-step workflows where each action can trigger additional analysis.
* Heartbeat Mechanism
The heartbeat thread ensures the agent remains alive even without external input. It drives two critical functions:
1. **Latent reflection** - the agent can think without external prompting
2. **Periodic maintenance** - memory auto-save, orphan detection, etc.
** Heartbeat Configuration Variables
#+begin_src lisp :tangle ../library/loop.lisp
(defvar *auto-save-interval* 300
"Interval in seconds between automatic memory saves.
Defaults to 300 seconds (5 minutes). Set via MEMORY_AUTO_SAVE_INTERVAL env var.")
(defvar *heartbeat-save-counter* 0
"Tracks heartbeats since last save, used to calculate auto-save timing.")
#+end_src
** start-heartbeat: The Pulsing Heart
#+begin_src lisp :tangle ../library/loop.lisp
(defun start-heartbeat ()
"Starts the background heartbeat thread.
The heartbeat runs in a dedicated thread to avoid blocking the main
signal processing loop. Each heartbeat:
1. Injects a :HEARTBEAT signal into the metabolic pipeline
2. Checks if memory should be auto-saved (based on interval ratio)
Configuration via environment:
- HEARTBEAT_INTERVAL: Seconds between heartbeats (default: 60)
- MEMORY_AUTO_SAVE_INTERVAL: Seconds between auto-saves (default: 300)"
(let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60))
(auto-save (or (ignore-errors (parse-integer (uiop:getenv "MEMORY_AUTO_SAVE_INTERVAL"))) *auto-save-interval*)))
(setf *auto-save-interval* auto-save)
(setf *heartbeat-save-counter* 0)
(setf *heartbeat-thread*
(bt:make-thread
(lambda ()
(loop
;; Wait for interval
(sleep interval)
;; Update counter and check if it's time to save
(incf *heartbeat-save-counter*)
(when (>= *heartbeat-save-counter* (/ *auto-save-interval* interval))
(setf *heartbeat-save-counter* 0)
(save-memory-to-disk))
;; Inject heartbeat signal - this runs through the full pipeline
;; allowing the agent to do latent reflection even with no input
(inject-stimulus
(list :type :EVENT
:payload (list :sensor :heartbeat
:unix-time (get-universal-time)))))
:name "opencortex-heartbeat"))))
#+end_src
* Main Entry Point
** Shutdown Configuration
#+begin_src lisp :tangle ../library/loop.lisp
(defvar *shutdown-save-enabled* t
"When T, save memory to disk on graceful shutdown.
Disable for testing or when memory persistence is handled externally.")
#+end_src
** main: System Bootstrap and Idle Loop
The main function orchestrates system startup:
1. Load environment variables from ~/.local/share/opencortex/.env
2. Restore memory from previous snapshot (crash recovery)
3. Initialize actuators and load all skills
4. Start the heartbeat thread
5. Register SIGINT handler for graceful Ctrl+C shutdown
6. Enter idle loop (sleeping in 1-hour increments)
#+begin_src lisp :tangle ../library/loop.lisp
(defun main ()
"Entry point for OpenCortex. Initializes the system and enters idle loop.
Startup sequence:
1. Load environment from ~/.local/share/opencortex/.env
2. Restore memory from disk (if snapshot exists)
3. Initialize actuators (shell, cli, system)
4. Load all skills from SKILLS_DIR
5. Start heartbeat thread
6. Register SIGINT handler for graceful shutdown
7. Enter idle loop (sleeps in DAEMON_SLEEP_INTERVAL chunks)
The idle loop checks for interrupts and saves memory before exit."
;; Step 1: Load environment variables from standard location
(let* ((home (uiop:getenv "HOME"))
(env-file (uiop:merge-pathnames*
".local/share/opencortex/.env"
(uiop:ensure-directory-pathname home))))
(when (uiop:file-exists-p env-file)
(cl-dotenv:load-env env-file)))
;; Step 2: Crash recovery - load memory from previous snapshot
(load-memory-from-disk)
;; Step 3-4: Initialize actuators and load skills
(initialize-actuators)
(initialize-all-skills)
;; Step 5: Start the heartbeat
(start-heartbeat)
;; Step 6: Register graceful shutdown handler
;; SBCL-specific: catches Ctrl+C (SIGINT) and saves before exit
#+sbcl
(sb-sys:enable-interrupt sb-unix:sigint
(lambda (sig code scp)
(declare (ignore sig code scp))
(harness-log "SHUTDOWN: SIGINT received. Saving memory...")
(when *shutdown-save-enabled*
(save-memory-to-disk))
(uiop:quit 0)))
;; Step 7: Idle loop - sleep in chunks, checking for interrupts
(let ((sleep-interval (or (ignore-errors
(parse-integer (uiop:getenv "DAEMON_SLEEP_INTERVAL")))
3600)))
(loop
;; Check for interrupt before each sleep cycle
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
(harness-log "SHUTDOWN: Interrupt flag set. Saving memory...")
(when *shutdown-save-enabled*
(save-memory-to-disk))
(return))
;; Sleep in configured intervals (default: 1 hour)
(sleep sleep-interval))))
#+end_src

229
harness/manifest.org Normal file
View File

@@ -0,0 +1,229 @@
#+TITLE: Manifest (opencortex.asd)
#+AUTHOR: Amr
#+FILETAGS: :harness:system:
#+STARTUP: content
* Manifest (opencortex.asd)
** Architectural Intent: The Thin Harness Philosophy
The ~opencortex.asd~ file is the physical blueprint of the Lisp Machine. It uses **ASDF** (Another System Definition Facility) to orchestrate compilation and loading of all harness modules.
The core design principle is *Thin Harness, Fat Skills*:
- **Harness** = The minimal, unbreakable core (protocol, signal processing, memory)
- **Skills** = The intelligence layer (policy, validation, actuation, LLM integration)
This separation means:
- The harness rarely changes (immune system)
- Skills can be hot-loaded, modified, and swapped without touching the core
- Bugs in skills don't crash the system
** Why ASDF?**
ASDF is the de facto standard for Common Lisp project management. It:
1. Handles dependency resolution and loading order
2. Compiles files in the right order (preventing "undefined function" errors)
3. Supports system building for deployment
4. Integrates with Quicklisp for dependency management
* The Build Pipeline
#+begin_src mermaid
flowchart TD
Org[Literate Org Files] -- Org-Babel Tangle --> Lisp[Source .lisp Files]
Lisp --> ASDF[ASDF Manifest: opencortex.asd]
ASDF --> Loader[SBCL Compiler / Loader]
Loader --> Image[Live Harness Image]
Image -- Build --> Binary[Standalone Binary]
subgraph Skills["Skills Layer (Dynamic)"]
S1[Policy Skill]
S2[Bouncer Skill]
S3[LLM Gateway]
S4[...other skills]
end
Image --> Skills
#+end_src
* Design Decisions
** Strict Serial Loading
The harness uses ~:serial t~ in the ASDF definition. This means:
1. Files are loaded in order: package → skills → communication → memory → context → perceive → reason → act → loop
2. ~package.lisp~ is always loaded before any code that uses its symbols
3. ~skills.lisp~ (defining macros like ~defskill~, ~def-cognitive-tool~) loads before skills
This eliminates "macro not found" errors that plague non-linear loading systems.
** Why Not Module Dependencies?**
Traditional ASDF uses ~:depends-on~ to declare dependencies. We use ~:serial t~ because:
1. *Explicit is better than implicit* - the loading order is visible in one place
2. *Prevents circular dependencies* - skills are loaded after the harness, never before
3. *Simpler debugging* - when something fails, the loading order is always clear
** Isolation of Tests
The testing system (~:opencortex/tests~) is separate from the production system (~:opencortex~). This means:
- Production deployments don't load FiveAM (saves memory, reduces attack surface)
- Tests can be run independently: ~(ql:quickload :opencortex/tests)~
- Test data doesn't pollute the production image
* System Definitions
** Main Harness System
#+begin_src lisp :tangle ../opencortex.asd
(defsystem :opencortex
:name "opencortex"
:author "Amr"
:version "0.1.0"
:license "AGPLv3"
:description "The Probabilistic-Deterministic Lisp Machine Harness"
:depends-on (:usocket ; TCP socket networking
:bordeaux-threads ; Threading (heartbeat, async sensors)
:dexador ; HTTP client (LLM APIs)
:uiop ; Portable I/O, file operations
:cl-dotenv ; Environment variable loading
:cl-ppcre ; Regular expressions (parsing)
:hunchentoot ; HTTP server (optional web interface)
:ironclad ; Cryptography (Merkle hashing)
:str ; String utilities
:cl-json ; JSON parsing/serialization
:uuid) ; UUID generation for org-mode IDs
:serial t ; Load files in order listed below
:components ((:file "library/package") ; Package definitions, core vars
(:file "library/skills") ; Skill engine, cognitive tools
(:file "library/communication") ; Protocol, framing, validation
(:file "library/memory") ; Org-object store, snapshots
(:file "library/context") ; Context assembly, query
(:file "library/perceive") ; Stage 1: Sensory normalization
(:file "library/reason") ; Stage 2: Neural + deterministic
(:file "library/act") ; Stage 3: Actuation
(:file "library/loop")) ; Main entry, heartbeat
:build-operation "program-op"
:build-pathname "opencortex-server"
:entry-point "opencortex:main")
#+end_src
** Test System
#+begin_src lisp :tangle ../opencortex.asd
(defsystem :opencortex/tests
:depends-on (:opencortex ; The harness we're testing
:fiveam) ; Testing framework
:components ((:file "tests/communication-tests")
(:file "tests/pipeline-tests")
(:file "tests/act-tests")
(:file "tests/boot-sequence-tests")
(:file "tests/memory-tests")
(:file "tests/immune-system-tests"))
:perform (test-op (o s)
(uiop:symbol-call :fiveam :run! :communication-protocol-suite)
(uiop:symbol-call :fiveam :run! :pipeline-suite)
(uiop:symbol-call :fiveam :run! :safety-suite)
(uiop:symbol-call :fiveam :run! :boot-suite)
(uiop:symbol-call :fiveam :run! :memory-suite)
(uiop:symbol-call :fiveam :run! :immune-suite)))
#+end_src
** TUI Client System
#+begin_src lisp :tangle ../opencortex.asd
(defsystem :opencortex/tui
:depends-on (:opencortex ; The daemon we're connecting to
:croatoan ; Terminal UI library
:usocket ; Socket communication
:bordeaux-threads) ; Background listening thread
:components ((:file "library/tui-client")))
#+end_src
* The Harness Boundary Contract
** Why a Boundary Contract?
The harness is the immune system of OpenCortex. If it grows fat (accumulating features, dependencies, complexity), it becomes harder to:
- Verify for security
- Debug when things go wrong
- Maintain across versions
The Boundary Contract defines what IS the harness vs. what belongs in skills.
** Primary Boundary Files
| File | Purpose | Modification |
|------|---------|--------------|
| ~harness/*.org~ | Literate source of truth | Only via Org edits + tangle |
| ~opencortex.asd~ | System manifest | Only via Org edits + tangle |
| ~library/*.lisp~ | Tangled from .org | NEVER edit directly |
** Generated Artifacts (NOT Primary)
The ~library/*.lisp~ files are tangles from the ~harness/*.org~ files. They are derivative artifacts. Direct modification violates the Literate Granularity standard.
** Protected Paths
The Policy skill guards these paths by default:
#+begin_src lisp
(defvar *modularity-protected-paths*
'("harness/"
"opencortex.asd"
"library/package.lisp"
"library/communication.lisp"
"library/memory.lisp"
"library/context.lisp"
"library/perceive.lisp"
"library/reason.lisp"
"library/act.lisp"
"library/loop.lisp"))
#+end_src
Any agent action proposing to modify these files must include a ~:modularity-justification~ field explaining why the change cannot be implemented as a skill.
** Enforcement Chain
1. *Policy Skill* (priority 500) - Checks for missing justifications
2. *Bouncer Skill* (priority 100) - Intercepts unauthorized modifications
3. *Git Hooks* (optional) - Prevents direct .lisp commits
* Quick Reference
** Building the System
#+begin_src bash
# Development: Load source
(ql:quickload :opencortex)
# Build standalone binary
(asdf:make :opencortex)
# Run tests
(ql:quickload :opencortex/tests)
(asdf:test-system :opencortex/tests)
#+end_src
** Loading Order
1. ~library/package.lisp~ - Creates ~:opencortex~ package
2. ~library/skills.lisp~ - Defines ~defskill~, ~def-cognitive-tool~ macros
3. ~library/communication.lisp~ - Protocol, framing, validation
4. ~library/memory.lisp~ - Org-object, Merkle tree, snapshots
5. ~library/context.lisp~ - Context assembly functions
6. ~library/perceive.lisp~ - Stage 1: Perceive gate
7. ~library/reason.lisp~ - Stage 2: Reason (think + verify)
8. ~library/act.lisp~ - Stage 3: Act (dispatch + execute)
9. ~library/loop.lisp~ - Main entry point, heartbeat

View File

@@ -31,14 +31,14 @@ flowchart TD
#+end_src
** Package Context
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(in-package :opencortex)
#+end_src
** The Object Repository
The `*memory*` is the global hash table that holds every Org element by its unique ID. This is the "live RAM" of the agent's memory.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defvar *memory* (make-hash-table :test 'equal))
(defvar *history-store* (make-hash-table :test 'equal)
@@ -48,15 +48,19 @@ The `*memory*` is the global hash table that holds every Org element by its uniq
** The Data Structure (org-object)
Every element in the Memex (headlines, paragraphs, etc.) is represented by an `org-object` structure. It contains both semantic metadata (attributes, content) and structural metadata (parent/child pointers, Merkle hashes).
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defstruct org-object
id type attributes content vector parent-id children version last-sync hash)
;; Enable serialization via make-load-form (standard CL)
(defmethod make-load-form ((obj org-object) &optional env)
(make-load-form-saving-slots obj :environment env))
#+end_src
** Merkle Tree Integrity (compute-merkle-hash)
The `compute-merkle-hash` function ensures the cryptographic integrity of the knowledge graph. A node's hash depends on its own properties and the hashes of all its children. This creates a recursive fingerprint where any change to a single note propagates up to the root hash.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defun compute-merkle-hash (id type attributes content child-hashes)
"Computes a SHA-256 Merkle hash for a node based on its core properties and children's hashes."
(let* ((alist (loop for (k v) on attributes by #'cddr collect (cons k v)))
@@ -73,7 +77,7 @@ The `compute-merkle-hash` function ensures the cryptographic integrity of the kn
** Ingesting the AST (ingest-ast)
The `ingest-ast` function is the primary bridge between the external world (Emacs/JSON) and the internal Lisp machine. It recursively parses an Org-mode Abstract Syntax Tree (AST) into `org-object` structures and registers them in the store.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defun ingest-ast (ast &optional parent-id)
"Parses an Org AST into the recursive Lisp Memory with Merkle hashing."
(let* ((type (getf ast :type))
@@ -112,7 +116,7 @@ The `ingest-ast` function is the primary bridge between the external world (Emac
** Memory Snapshots (snapshot-memory)
Because objects are stored immutably in the `*history-store*`, a snapshot is a lightweight shallow copy of the active `*memory*` pointers. The system maintains a rolling buffer of 20 snapshots, allowing for near-instant, zero-cost rollback.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defvar *object-store-snapshots* nil)
(defun copy-hash-table (hash-table)
@@ -134,7 +138,7 @@ Because objects are stored immutably in the `*history-store*`, a snapshot is a l
** Memory Rollback (rollback-memory)
Restores the state of the Memex from one of the previous snapshots.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defun rollback-memory (&optional (index 0))
"Restores the Memory to a previously captured snapshot using immutable history pointers."
(let ((snapshot (nth index *object-store-snapshots*)))
@@ -144,10 +148,63 @@ Restores the state of the Memex from one of the previous snapshots.
(harness-log "MEMORY ERROR - Snapshot ~a not found." index))))
#+end_src
** Disk Persistence (save-memory / load-memory)
Essential for surviving crashes. Saves the in-memory hash tables to disk and loads them back on restart. The path is controlled by the `MEMORY_SNAPSHOT_PATH` environment variable.
#+begin_src lisp :tangle ../library/memory.lisp
(defvar *memory-snapshot-path* nil
"Path to the memory snapshot file. Set from MEMORY_SNAPSHOT_PATH env or default.")
(defun ensure-memory-snapshot-path ()
"Initializes the snapshot path from environment or default location."
(or *memory-snapshot-path*
(let ((env-path (uiop:getenv "MEMORY_SNAPSHOT_PATH")))
(setf *memory-snapshot-path*
(or env-path
(uiop:merge-pathnames* "memory.snap" (user-homedir-pathname)))))))
(defun save-memory-to-disk ()
"Serializes *memory* and *history-store* to disk for crash recovery.
Converts hash tables to alists for proper serialization."
(let ((path (ensure-memory-snapshot-path)))
(with-open-file (stream path :direction :output :if-exists :supersede :if-does-not-exist :create)
(format stream ";; OpenCortex Memory Snapshot~%")
(format stream ";; Created: ~a~%~%" (format nil "~a" (get-universal-time)))
(let ((memory-alist nil)
(history-alist nil))
(maphash (lambda (k v) (push (cons k v) memory-alist)) *memory*)
(maphash (lambda (k v) (push (cons k v) history-alist)) *history-store*)
(prin1 (list :memory memory-alist :history-store history-alist) stream)))
(harness-log "MEMORY - Saved to ~a" path)
path))
(defun load-memory-from-disk ()
"Loads *memory* and *history-store* from disk if the snapshot exists.
Reconstitutes alists into hash tables."
(let ((path (ensure-memory-snapshot-path)))
(when (uiop:file-exists-p path)
(handler-case
(with-open-file (stream path :direction :input)
(let ((data (read stream nil)))
(when data
(let ((memory-alist (getf data :memory))
(history-alist (getf data :history-store)))
(setf *memory* (make-hash-table :test 'equal :size (length memory-alist)))
(dolist (kv memory-alist)
(setf (gethash (car kv) *memory*) (cdr kv)))
(setf *history-store* (make-hash-table :test 'equal :size (length history-alist)))
(dolist (kv history-alist)
(setf (gethash (car kv) *history-store*) (cdr kv)))
(harness-log "MEMORY - Loaded from ~a (~a objects)" path (hash-table-size *memory*))))))
(error (c)
(harness-log "MEMORY WARNING - Failed to load snapshot: ~a" c))))
t))
#+end_src
** Lookup Utilities
Basic functions for retrieving objects by ID or type.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defun org-id-new ()
"Generates a new UUID string for Org-mode identification."
(string-downcase (format nil "~a" (uuid:make-v4-uuid))))
@@ -161,12 +218,22 @@ Basic functions for retrieving objects by ID or type.
(let ((results nil))
(maphash (lambda (id obj) (declare (ignore id)) (when (eq (org-object-type obj) type) (push obj results))) *memory*)
results))
(defun list-objects-with-attribute (attr-name value)
"Returns a list of all objects where ATTR-NAME matches VALUE."
(let ((results nil))
(maphash (lambda (id obj)
(declare (ignore id))
(let ((attrs (org-object-attributes obj)))
(when (equal (getf attrs attr-name) value)
(push obj results))))
*memory*)
results))
#+end_src
** Structural Helpers
Utility functions for AST traversal and path resolution.
#+begin_src lisp :tangle ../src/memory.lisp
#+begin_src lisp :tangle ../library/memory.lisp
(defun find-headline-missing-id (ast)
"Traverses an AST to find headlines that lack an :ID: property."
(when (listp ast)

View File

@@ -18,12 +18,18 @@ flowchart TD
#+end_src
** Public API Export
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defpackage :opencortex
(:use :cl)
(:export
;; --- communication protocol ---
#:frame-message
#:read-framed-message
#:PROTO-GET
#:LIST-OBJECTS-WITH-ATTRIBUTE
#:COSINE-SIMILARITY
#:VAULT-MASK-STRING
#:*VAULT-MEMORY*
#:parse-message
#:make-hello-message
#:validate-communication-protocol-schema
@@ -132,28 +138,54 @@ flowchart TD
#:find-headline-missing-id))
#+end_src
#+begin_src lisp :tangle ../library/package.lisp
(in-package :opencortex)
(defun proto-get (plist key)
"Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions."
(let* ((s (string key))
(up (intern (string-upcase s) :keyword))
(dn (intern (string-downcase s) :keyword)))
(or (getf plist up) (getf plist dn))))
#+end_src
#+end_src
#+begin_src lisp :tangle ../library/package.lisp
(in-package :opencortex)
(defun proto-get (plist key)
"Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions."
(let* ((s (string key))
(up (intern (string-upcase s) :keyword))
(dn (intern (string-downcase s) :keyword)))
(or (getf plist up) (getf plist dn))))
#+end_src
#+end_src
** Package Implementation
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(in-package :opencortex)
#+end_src
** Harness Logging State
The harness maintains a thread-safe circular log buffer to provide context for debugging and neural reasoning.
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defvar *system-logs* nil)
(defvar *logs-lock* (bt:make-lock "harness-logs-lock"))
(defvar *max-log-history* 100)
#+end_src
** Skills Registry
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defvar *skills-registry* (make-hash-table :test 'equal)
"Global registry of all loaded skills.")
#+end_src
** Skill Telemetry State
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defvar *skill-telemetry* (make-hash-table :test 'equal))
(defvar *telemetry-lock* (bt:make-lock "harness-telemetry-lock"))
#+end_src
@@ -161,7 +193,7 @@ The harness maintains a thread-safe circular log buffer to provide context for d
** Telemetry Implementation
The system tracks the performance and reliability of individual skills. This logic is currently preserved in the package layer for future expansion into a dedicated telemetry skill.
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defun harness-track-telemetry (skill-name duration status)
"Updates performance metrics for a specific skill. Status should be :success or :rejected."
(when skill-name
@@ -176,7 +208,7 @@ The system tracks the performance and reliability of individual skills. This log
** Cognitive Tool Registry
The Tool Registry allows the agent to interact with the physical world. Every tool must define a guard (for security) and a body (for execution).
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defvar *cognitive-tools* (make-hash-table :test 'equal))
(defstruct cognitive-tool
@@ -199,7 +231,7 @@ The Tool Registry allows the agent to interact with the physical world. Every to
** Harness Logging Implementation
Centralized logging function. It simultaneously writes to standard output and the in-memory circular buffer.
#+begin_src lisp :tangle ../src/package.lisp
#+begin_src lisp :tangle ../library/package.lisp
(defun harness-log (msg &rest args)
"Centralized logging for the harness."
(let ((formatted-msg (apply #'format nil msg args)))
@@ -210,3 +242,5 @@ Centralized logging function. It simultaneously writes to standard output and th
(format t "~a~%" formatted-msg)
(finish-output)))
#+end_src

222
harness/perceive.org Normal file
View File

@@ -0,0 +1,222 @@
#+TITLE: Stage 1: Perceive (perceive.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:perceive:
#+STARTUP: content
* Stage 1: Perceive (perceive.lisp)
** Architectural Intent: Sensory Normalization
The Perceive stage is the "sensory cortex" of OpenCortex. Its job is to take raw stimuli from the outside world and transform them into standardized Signals that the rest of the pipeline can process.
Raw stimuli come from diverse sources:
- Terminal input (CLI)
- Emacs org-mode buffers (via swank)
- Telegram/Signal messages
- Heartbeats (internal clock)
- Shell command outputs
Each source has its own format and protocol. Perceive normalizes all of them into the Signal format:
: (TYPE :EVENT META (...) PAYLOAD (...))
** Why Normalize?
Without normalization, each downstream component (Reason, Act) would need to understand each input format. With normalization:
1. The gateway layer (CLI, Emacs, Telegram) just sends raw messages
2. Perceive transforms them into Signals
3. Reason and Act work with a single, consistent format
4. Adding new input sources only requires gateway code, not changes to the core
** The Signal Format
Signals are property lists with a consistent structure:
| Key | Description |
|-----|-------------|
| :type | :EVENT, :REQUEST, :RESPONSE, :LOG |
| :payload | The actual content (sensor data, actions, etc.) |
| :meta | Metadata: source, session, reply stream |
| :status | Processing status: :perceived, :reasoned, :acted |
| :depth | Recursion depth for feedback loops |
| :approved-action | Set by Reason, executed by Act |
| :foveal-focus | ID of the node user is interacting with |
** Async vs Sync Processing
Some sensors (user input, chat messages) are processed asynchronously in dedicated threads. This prevents:
- A slow API call from blocking the entire system
- Race conditions when multiple inputs arrive simultaneously
Other sensors (heartbeats, interrupts) are processed synchronously to maintain ordering guarantees.
* Package Context
#+begin_src lisp :tangle ../library/perceive.lisp
(in-package :opencortex)
#+end_src
* Sensor Configuration
** Async Sensor Registry
#+begin_src lisp :tangle ../library/perceive.lisp
(defvar *async-sensors* '(:chat-message :delegation :user-command)
"Sensors that are processed in dedicated threads.
These sensors can block (waiting for API responses, user input, etc.)
so they run in separate threads to avoid blocking the main pipeline.
Other sensors (:heartbeat, :interrupt, :buffer-update) are processed
synchronously to maintain temporal ordering.")
#+end_src
** Foveal Focus State
#+begin_src lisp :tangle ../library/perceive.lisp
(defvar *foveal-focus-id* nil
"The Org ID of the node the user is currently interacting with.
This enables the reasoning engine to provide contextually relevant
responses. When editing a specific note, the agent knows which
note you're referring to without needing explicit ID references.
Updated on :point-update events from Emacs.")
#+end_src
* Stimulus Injection
** inject-stimulus: Entry Point
#+begin_src lisp :tangle ../library/perceive.lisp
(defun inject-stimulus (raw-message &key stream (depth 0))
"Inject a raw message into the signal processing pipeline.
RAW-MESSAGE is a property list that will be normalized into a Signal.
STREAM is an optional output stream for responses (used by TUI/CLI).
DEPTH tracks recursion depth for feedback loops.
This function determines whether to process synchronously or
asynchronously based on the sensor type, then calls process-signal
to run through the Perceive -> Reason -> Act pipeline.
Error handling: Uses restarts to prevent individual signals from
crashing the entire system. Failed signals are logged and dropped."
(let* ((payload (getf raw-message :payload))
(sensor (getf payload :sensor))
(meta (getf raw-message :meta))
(async-p (or (getf payload :async-p)
(member sensor *async-sensors*))))
;; Ensure metadata exists
(unless meta
(setf meta (list :SOURCE :SYSTEM :SESSION-ID "internal")))
;; Attach reply stream if provided
(when stream
(setf (getf meta :reply-stream) stream))
(setf (getf raw-message :meta) meta)
(if async-p
;; Async: process in dedicated thread
(bt:make-thread
(lambda ()
(restart-case
(handler-bind ((error (lambda (c)
(harness-log "ASYNC ERROR: ~a" c)
(invoke-restart 'skip-event))))
(process-signal raw-message))
(skip-event () nil)))
:name "opencortex-async-task")
;; Sync: process in main thread with recovery
(restart-case
(handler-bind ((error (lambda (c)
(harness-log "SYSTEM ERROR: ~a" c)
(invoke-restart 'skip-event))))
(process-signal raw-message))
(skip-event ()
(harness-log "SYSTEM RECOVERY: Stimulus dropped."))))))
#+end_src
* The Perceive Gate
** perceive-gate: Signal Normalization
#+begin_src lisp :tangle ../library/perceive.lisp
(defun perceive-gate (signal)
"Stage 1 of the metabolic pipeline: Normalize sensory input.
This function:
1. Logs the incoming signal for debugging
2. Handles special sensor types (:buffer-update, :point-update, etc.)
3. Updates the Memory graph with incoming data
4. Tracks foveal focus (user's current node)
5. Sets :status to :perceived
Modifies the signal in place and returns it for the next stage.
Memory snapshots are taken before AST updates to enable rollback
if the update causes issues."
(let* ((payload (getf signal :payload))
(type (getf signal :type))
(meta (getf signal :meta))
(sensor (getf payload :sensor)))
;; Log the incoming signal for debugging
(harness-log "GATE [Perceive]: ~a (~a) [Source: ~s]"
type (or sensor "no-sensor") (getf meta :source))
;; Handle EVENT type sensors
(cond ((eq type :EVENT)
(case sensor
;; Org buffer was modified - update memory
(:buffer-update
(let ((ast (getf payload :ast)))
(when ast
(snapshot-memory) ; Enable rollback if update causes issues
(ingest-ast ast))))
;; Point moved to different org node - update focus
(:point-update
(let ((element (getf payload :element)))
(when element
(snapshot-memory)
;; Track foveal focus for contextual reasoning
(setf *foveal-focus-id*
(ignore-errors (getf element :id)))
(ingest-ast element))))
;; System interrupt - trigger shutdown
(:interrupt
(bt:with-lock-held (*interrupt-lock*)
(setf *interrupt-flag* t)))))
;; Log responses from actuators
((eq type :RESPONSE)
(harness-log "GATE [Perceive]: Act Result -> ~a"
(getf payload :status))))
;; Update signal status
(setf (getf signal :status) :perceived)
(setf (getf signal :foveal-focus) *foveal-focus-id*)
signal))
#+end_src
** Sensor Types Reference
| Sensor | Source | Processing | Description |
|--------|--------|------------|-------------|
| :user-input | CLI/TUI | Async | Text input from terminal |
| :chat-message | Telegram/Signal | Async | Messages from messaging apps |
| :heartbeat | Internal | Sync | Periodic maintenance trigger |
| :buffer-update | Emacs | Sync | Org buffer was modified |
| :point-update | Emacs | Sync | Cursor moved to different headline |
| :interrupt | System | Sync | SIGINT received |
| :tool-output | Internal | Sync | Result from cognitive tool |
| :loop-error | Internal | Sync | Error during signal processing |

444
harness/reason.org Normal file
View File

@@ -0,0 +1,444 @@
#+TITLE: Stage 2: Reason (reason.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:reason:
#+STARTUP: content
* Stage 2: Reason (reason.lisp)
** Architectural Intent: The Dual-Engine Cognitive Architecture
The Reason stage implements the core innovation of OpenCortex: the separation of probabilistic reasoning (neural/LLM) from deterministic verification (logic/safety).
This dual-engine design solves a fundamental problem in AI safety:
1. *Probabilistic Engine* - Uses LLMs for semantic understanding, natural language generation, and complex reasoning. It is powerful but can hallucinate, make syntax errors, or propose unsafe actions.
2. *Deterministic Engine* - Uses formal verification (skills) to check LLM proposals before execution. It is slower but provably correct.
The LLM proposes; the skills verify. This is the "Bouncer Pattern" - the deterministic engine is literally a bouncer that checks the LLM's proposals at the door before letting them through to execution.
** Why Plists for Communication?
The Reason stage communicates exclusively through property lists (plists). This design choice reflects the homoiconic nature of Lisp - plists are native data structures that can be read, written, and manipulated by the same code that processes them.
A plist message like:
: (TYPE :REQUEST TARGET :CLI PAYLOAD (ACTION :MESSAGE TEXT "Hello"))
Is simultaneously:
- Human-readable text
- Machine-parseable data structure
- Executable Lisp code
This means the reasoning pipeline can generate, modify, and execute its own communication protocol without external parsing.
* Package Context
#+begin_src lisp :tangle ../library/reason.lisp
(in-package :opencortex)
#+end_src
* Probabilistic Engine (Neural/LLM Integration)
The probabilistic engine is responsible for all neural/LLM operations. It maintains a registry of provider backends and implements a cascading failover mechanism.
** Backend Registry Variables
#+begin_src lisp :tangle ../library/reason.lisp
(defvar *probabilistic-backends* (make-hash-table :test 'equal)
"Registry mapping provider keywords (:openrouter, :ollama) to their calling functions.")
(defvar *provider-cascade* nil
"Ordered list of provider keywords to try. First available provider wins.")
(defvar *model-selector-fn* nil
"Optional function that selects a specific model for each provider.
Signature: (funcall fn provider context) => model-name-string")
(defvar *consensus-enabled-p* nil
"When T, run multiple providers and compare results for critical decisions.")
#+end_src
** register-probabilistic-backend: Backend Registration
#+begin_src lisp :tangle ../library/reason.lisp
(defun register-probabilistic-backend (name fn)
"Register a neural provider backend.
NAME is a keyword like :openrouter or :ollama.
FN is a function with signature: (funcall fn prompt system-prompt &key model)
returning either:
- (list :status :success :content \"response text\")
- (list :status :error :message \"error description\")
- a simple string on success
Example registration:
(register-probabilistic-backend :openrouter #'openrouter-call)"
(setf (gethash name *probabilistic-backends*) fn))
#+end_src
** probabilistic-call: Cascade Dispatch
#+begin_src lisp :tangle ../library/reason.lisp
(defun probabilistic-call (prompt &key
(system-prompt "You are the Probabilistic engine.")
(cascade nil)
(context nil))
"Dispatch a neural request through the provider cascade.
PROMPT - The user's query or task description.
SYSTEM-PROMPT - Instructions for how the LLM should behave.
CASCADE - Override the default provider cascade.
CONTEXT - Current signal context (for model selection).
Returns the LLM response as a string, or a failure plist if all providers fail.
The cascade mechanism ensures reliability: if OpenRouter is rate-limited,
it automatically falls back to OpenAI, then Anthropic, etc."
(let ((backends (or cascade *provider-cascade*)))
(or (dolist (backend backends)
(let ((backend-fn (gethash backend *probabilistic-backends*)))
(when backend-fn
(harness-log "PROBABILISTIC: Attempting backend ~a..." backend)
;; Optional model selection based on context
(let* ((model (when *model-selector-fn*
(funcall *model-selector-fn* backend context)))
(result (if model
(funcall backend-fn prompt system-prompt :model model)
(funcall backend-fn prompt system-prompt))))
;; Normalize result format
(cond ((and (listp result) (eq (getf result :status) :success))
(return (getf result :content)))
((stringp result)
(return result))
(t
(harness-log "PROBABILISTIC: Backend ~a failed: ~a"
backend (getf result :message))))))))
;; All providers failed
(list :type :LOG
:payload (list :text "Neural Cascade Failure: All providers exhausted.")))))
#+end_src
* Cognitive Proposal Generation (Think)
The `think` function is the heart of the probabilistic engine. It constructs a prompt from context, sends it to the LLM, and parses the response into a structured action.
** strip-markdown: Clean LLM Output
#+begin_src lisp :tangle ../library/reason.lisp
(defun strip-markdown (text)
"Strip markdown formatting from LLM output.
LLMs often wrap their responses in code fences (```lisp ...```).
This function removes those markers to extract the raw plist.
Handles:
- Leading code fences with language tags: ```lisp
- Trailing code fences: ```
- Orphan closing fences: ```"
(if (and text (stringp text))
(let ((cleaned text))
(setf cleaned (cl-ppcre:regex-replace-all "^```[a-z]*\\n" cleaned ""))
(setf cleaned (cl-ppcre:regex-replace-all "\\n```$" cleaned ""))
(setf cleaned (cl-ppcre:regex-replace-all "```" cleaned ""))
(string-trim '(#\Space #\Newline #\Tab) cleaned))
text))
#+end_src
** normalize-plist-keywords: Fix LLM Keyword Output
#+begin_src lisp :tangle ../library/reason.lisp
(defun normalize-plist-keywords (plist)
"Normalize all keys in a plist to keywords.
LLMs often return plists with unquoted keys: (TYPE REQUEST ...)
instead of keyword syntax: (:TYPE :REQUEST ...)
This function converts all symbol keys to their keyword equivalents,
making the plist compatible with standard Lisp property accessors.
Example transformation:
(TYPE REQUEST PAYLOAD (ACTION MESSAGE TEXT \"Hi\"))
=> (:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT \"Hi\"))"
(when (listp plist)
(loop for (k . rest) on plist by #'cddr
collect (if (and (symbolp k) (not (keywordp k)))
(intern (string k) :keyword)
k)
collect (car rest))))
#+end_src
** think: Generate Action Proposal
#+begin_src lisp :tangle ../library/reason.lisp
(defun think (context)
"Generate a Lisp action proposal based on current context.
This is the core cognitive function. It:
1. Finds the most relevant skill based on context
2. Assembles global awareness (memory context, system logs)
3. Constructs a detailed prompt with available tools
4. Calls the LLM via probabilistic-call
5. Parses the LLM response into a structured action plist
The LLM is instructed to respond with exactly ONE plist, never prose.
This constraint makes parsing deterministic and prevents rambling.
Returns a plist with structure:
(:TYPE :REQUEST :TARGET :CLI :PAYLOAD (:ACTION :MESSAGE :TEXT \"...\"))"
;; Gather context components
(let* ((active-skill (find-triggered-skill context))
(tool-belt (generate-tool-belt-prompt))
(global-context (context-assemble-global-awareness))
(system-logs (context-get-system-logs))
(assistant-name (or (uiop:getenv "MEMEX_ASSISTANT") "Agent")))
;; Generate prompt from skill or raw text
(let* ((prompt-generator (when active-skill
(skill-probabilistic-prompt active-skill)))
(raw-prompt (if prompt-generator
(funcall prompt-generator context)
;; Fallback: use raw user input
(let ((p (proto-get (proto-get context :payload) :text)))
(if (and p (stringp p))
p
"Maintain metabolic stasis."))))
(system-prompt (format nil
"IDENTITY: ~a
You are a component of the OpenCortex neurosymbolic AI agent.
Your task is to generate exactly ONE valid Lisp plist response.
MANDATE: Respond with ONE Lisp plist. Never output prose.
IMPORTANT: To reply to the user, you MUST use:
(:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT \"<Response Text>\"))
To call a tool, you MUST use:
(:TYPE :REQUEST :TARGET :TOOL :ACTION :CALL :TOOL \"<name>\" :ARGS (:arg1 \"val\"))
MANDATORY VALIDATION RULE: Before declaring any Lisp code edit complete,
you MUST call the `:validate-lisp` tool with the proposed code. If the tool
returns `:status :error`, read the `:reason` and `:failed` fields, fix the
defect, and re-validate. You are strictly forbidden from relying on your
own paren-balancing or syntax intuition.
PROVIDER RULE: Always use the default cascade provider unless a specific
model or capability is required for the task.
AVAILABLE TOOLS:
~a
GLOBAL CONTEXT:
~a
RECENT LOGS:
~a"
assistant-name
tool-belt
global-context
system-logs)))
;; Call LLM and process response
(let* ((thought (probabilistic-call raw-prompt
:system-prompt system-prompt
:context context))
(cleaned (strip-markdown thought))
(meta (proto-get context :meta))
(source (proto-get meta :source)))
(when cleaned
(harness-log "THINK: LLM raw output = ~a"
(subseq cleaned 0 (min 200 (length cleaned)))))
;; Parse LLM response
(if (and cleaned (stringp cleaned) (> (length cleaned) 0))
(let ((*read-eval* nil))
(if (char= (char cleaned 0) #\()
;; Response starts with paren - try to parse as plist
(handler-case
(let ((parsed (read-from-string cleaned)))
(when parsed
(harness-log "THINK: parsed = ~a" parsed)
;; Normalize keyword keys (LLM often returns TYPE instead of :TYPE)
(let ((parsed-normalized (normalize-plist-keywords parsed))
(type (proto-get parsed :TYPE))
(target (or (proto-get parsed :TARGET)
(proto-get parsed :target))))
(cond
;; Recognized message type - use directly
((member type '(:REQUEST :EVENT :STATUS :RESPONSE))
(unless (proto-get parsed :target)
(setf (getf parsed :target) (or source :CLI)))
parsed-normalized)
;; Tool call detected - wrap in standard envelope
((or (eq target :TOOL)
(eq target :tool)
(getf parsed :TOOL)
(getf parsed :tool)
(and (listp parsed)
(listp (car parsed))
(keywordp (caar parsed))))
(list :TYPE :REQUEST
:TARGET :TOOL
:PAYLOAD (normalize-plist-keywords parsed)))
;; Unknown format - treat as user message
(t
(list :TYPE :REQUEST
:TARGET (or source :CLI)
:PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned)))))))
(error (c)
(harness-log "THINK ERROR: ~a" c)
(list :TYPE :REQUEST
:TARGET (or source :CLI)
:PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned))))
;; No leading paren - treat as plain text message
(list :TYPE :REQUEST
:TARGET (or source :CLI)
:PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned))))
;; No response from LLM
thought)))))
#+end_src
* Deterministic Engine (Formal Verification)
The deterministic engine runs all registered skills' verification functions. This is where safety checks, policy enforcement, and skill-specific processing happen.
** deterministic-verify: Skill Chain Verification
#+begin_src lisp :tangle ../library/reason.lisp
(defun deterministic-verify (proposed-action context)
"Run all skill deterministic gates on a proposed action.
Each skill can define a deterministic function that either:
- Passes the action through unchanged
- Modifies the action (adds explanation, changes target, etc.)
- Blocks the action (returns a :LOG message instead)
Skills are sorted by priority (highest first). A skill with higher
priority can intercept and modify actions before lower-priority
skills see them.
The Bouncer Pattern: If any skill returns a :LOG or :EVENT type,
processing stops and that message is returned immediately. This
allows skills to veto actions.
Example skill chain:
1. Policy skill (priority 500) - checks for missing explanations
2. Protocol validator (priority 95) - validates message schema
3. Shell actuator guard (priority 50) - checks command whitelist"
(let ((current-action proposed-action)
(skills nil))
;; Collect all skills with deterministic functions
(maphash (lambda (name skill)
(declare (ignore name))
(when (skill-deterministic-fn skill)
(push skill skills)))
*skills-registry*)
;; Sort by priority (highest first)
(setf skills (sort skills #'> :key #'skill-priority))
;; Run each skill's gate
(dolist (skill skills)
(let ((trigger (skill-trigger-fn skill))
(gate (skill-deterministic-fn skill)))
;; Skill activates if no trigger or trigger returns true
(when (or (null trigger)
(ignore-errors (funcall trigger context)))
;; Run the gate
(let ((next-action (funcall gate current-action context)))
(let ((original-type (proto-get current-action :type)))
;; Check if skill intercepted (returned LOG/EVENT instead of REQUEST)
(when (and (listp next-action)
(member (proto-get next-action :type)
'(:LOG :EVENT :log :event))
(or (not (member original-type '(:LOG :EVENT :log :event)))
(not (eq next-action current-action))))
;; Skill blocked or modified - stop processing
(harness-log "DETERMINISTIC: Intercepted by skill '~a'"
(skill-name skill))
(return-from deterministic-verify next-action)))
;; Action passed through - continue to next skill
(setf current-action next-action)))))
;; Return final action (may be modified by skills, or original if all passed)
current-action))
#+end_src
* Reason Gate (Pipeline Stage)
** reason-gate: The Stage Function
#+begin_src lisp :tangle ../library/reason.lisp
(defun reason-gate (signal)
"Stage 2 of the metabolic pipeline: Reason.
Transforms perceived signals into approved actions by combining:
1. Probabilistic reasoning (LLM generates proposal)
2. Deterministic verification (skills validate proposal)
Only processes :EVENT signals with :user-input or :chat-message sensors.
Other signals pass through unchanged (heartbeats, tool outputs, etc.).
Modifies the signal in place by setting:
- :approved-action - The final verified action, or NIL
- :status - :reasoned
Returns the modified signal."
(let* ((type (proto-get signal :type))
(payload (proto-get signal :payload))
(sensor (proto-get payload :sensor)))
;; Only reason about user input, not internal signals
(unless (and (eq type :EVENT)
(member sensor '(:user-input :chat-message)))
(return-from reason-gate signal))
;; Generate proposal via LLM
(let ((candidate (think signal)))
(harness-log "REASON: candidate type = ~a" (type-of candidate))
;; Validate candidate is a proper plist (not an error string or symbol)
(if (and candidate
(listp candidate)
(or (keywordp (car candidate))
(eq (car candidate) 'TYPE)
(eq (car candidate) 'type)))
;; Valid proposal - run through deterministic verification
(setf (getf signal :approved-action)
(deterministic-verify candidate signal))
;; Invalid response - log and drop
(progn
(harness-log "REASON: Invalid candidate type ~a, dropping"
(type-of candidate))
(setf (getf signal :approved-action) nil)))
(setf (getf signal :status) :reasoned)
signal)))
#+end_src

280
harness/setup.org Normal file
View File

@@ -0,0 +1,280 @@
#+TITLE: Zero-to-One Setup (setup.org)
#+AUTHOR: Amr
#+FILETAGS: :harness:setup:
#+STARTUP: content
* Zero-to-One Setup (setup.org)
The ~setup.org~ file defines the automated installation and initialization sequence for the OpenCortex.
** The Installer Script (opencortex.sh)
#+begin_src bash :tangle ../opencortex.sh
#!/bin/bash
set -e
PORT=9105
HOST="localhost"
RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; YELLOW='\033[0;33m'; NC='\033[0m'
command_exists() { command -v "$1" >/dev/null 2>&1; }
# Resolve symlinks to find the actual repository location
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
export SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
# Load environment variables if they exist
if [ -f "$SCRIPT_DIR/.env" ]; then
while IFS="=" read -r key value || [ -n "$key" ]; do
if [[ $key =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
val=$(echo "$value" | sed "s/^\"//;s/\"$//")
export "$key=$val"
fi
done < "$SCRIPT_DIR/.env"
[ -n "$ORG_AGENT_DAEMON_PORT" ] && PORT=$ORG_AGENT_DAEMON_PORT
[ -n "$DAEMON_HOST" ] && HOST=$DAEMON_HOST
fi
# --- 1. BOOTSTRAP ---
# If the script is run standalone, it clones the full repo and restarts itself.
if [ ! -d "$SCRIPT_DIR/.git" ] && [ ! -d "$HOME/.opencortex" ] && [[ ! "$(pwd)" =~ "opencortex" ]]; then
echo -e "${BLUE}=== OpenCortex: Zero-to-One Bootstrapper ===${NC}"
git clone ssh://git@10.10.10.201:2222/amr/opencortex.git ~/.opencortex
cd ~/.opencortex && git submodule update --init --recursive
exec ./opencortex.sh "$@"
fi
# --- 2. SETUP ---
setup_system() {
NON_INTERACTIVE=false
for arg in "$@"; do
if [ "$arg" == "--non-interactive" ]; then NON_INTERACTIVE=true; fi
done
echo -e "${BLUE}=== OpenCortex: Initializing System ===${NC}"
echo -e "${YELLOW}--- Installing System Dependencies ---${NC}"
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y sbcl emacs-nox rlwrap netcat-openbsd curl git socat libssl-dev libncurses5-dev libffi-dev zlib1g-dev libsqlite3-dev
fi
if [ ! -d "$HOME/quicklisp" ]; then
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))"
rm quicklisp.lisp
fi
cd "$SCRIPT_DIR"
if [ ! -f .env ]; then
if [ "$NON_INTERACTIVE" = true ]; then
echo "Non-interactive mode: Using environment variables for .env creation."
cp .env.example .env
[ -n "$MEMEX_USER" ] && sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$MEMEX_USER\"|" .env
[ -n "$MEMEX_ASSISTANT" ] && sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$MEMEX_ASSISTANT\"|" .env
[ -n "$OPENROUTER_API_KEY" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$OPENROUTER_API_KEY\"|" .env
[ -n "$MEMEX_DIR" ] && sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$MEMEX_DIR\"|" .env
else
cp .env.example .env
echo -e "\n${YELLOW}--- Identity Configuration ---${NC}"
read -p "Your Name [User]: " user_name < /dev/tty
user_name=${user_name:-User}
sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$user_name\"|" .env
read -p "Agent Name [OpenCortex]: " agent_name < /dev/tty
agent_name=${agent_name:-OpenCortex}
sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$agent_name\"|" .env
echo -e "\n${YELLOW}--- LLM Configuration ---${NC}"
read -p "OpenRouter API Key: " openrouter_key < /dev/tty
[ -n "$openrouter_key" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$openrouter_key\"|" .env
echo -e "\n${YELLOW}--- Memex Folder Structure ---${NC}"
read -p "Memex Root [\$HOME/memex]: " memex_dir < /dev/tty
memex_dir=${memex_dir:-\$HOME/memex}
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$memex_dir\"|" .env
fi
# Hydrate default paths
M_DIR=$(grep MEMEX_DIR .env | cut -d'"' -f2 | sed "s|\$HOME|$HOME|")
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$SCRIPT_DIR/skills\"|" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$M_DIR/notes\"|" .env
mkdir -p "$M_DIR" "$M_DIR/notes" "$M_DIR/areas" "$M_DIR/resources" "$M_DIR/archives" "$M_DIR/system" "$M_DIR/inbox" "$M_DIR/daily" "$M_DIR/projects"
fi
mkdir -p library
for f in harness/*.org skills/*.org; do
emacs -Q --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || true
done
mkdir -p "$HOME/.local/bin"
ln -sf "$SCRIPT_DIR/opencortex.sh" "$HOME/.local/bin/opencortex"
for shell_config in "$HOME/.bashrc" "$HOME/.profile"; do
if [ -f "$shell_config" ]; then
if ! grep -q ".local/bin" "$shell_config"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$shell_config"
fi
fi
done
export PATH="$HOME/.local/bin:$PATH"
echo -e "${YELLOW}--- Compiling and Loading OpenCortex ---${NC}"
sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval "(ql:quickload '(:opencortex :croatoan))"
if [ $? -ne 0 ]; then
echo -e "${RED}✗ Compilation failed.${NC}"
exit 1
fi
if [ "$NON_INTERACTIVE" = true ]; then
echo "Setup complete (Non-interactive)."
exit 0
fi
echo -e "${YELLOW}--- Finalizing: Awakening the Brain ---${NC}"
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
success=false
for i in {1..30}; do
if nc -z localhost $PORT 2>/dev/null; then success=true; break; fi
sleep 2
echo -n "."
done
if [ "$success" = true ]; then
echo -e "\n${GREEN}✓ Brain is alive on port $PORT.${NC}"
exit 0
else
echo -e "\n${RED}✗ Brain failed to wake up.${NC}"
exit 1
fi
}
# --- 3. COMMAND ROUTER ---
COMMAND=$1
[ -z "$COMMAND" ] && COMMAND="cli"
shift || true
DEFAULT_PORT=9105
DEFAULT_HOST="localhost"
TARGET_PORT=${PORT:-$DEFAULT_PORT}
TARGET_HOST=${HOST:-$DEFAULT_HOST}
# If uninitialized, force setup.
if [ ! -f "$SCRIPT_DIR/library/package.lisp" ] || [ ! -f "$SCRIPT_DIR/.env" ]; then
COMMAND="setup"
fi
case "$COMMAND" in
setup)
setup_system "$@"
;;
--boot|boot)
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
if [ -f "$SCRIPT_DIR/.env" ]; then
export OPENROUTER_API_KEY=$(grep OPENROUTER_API_KEY "$SCRIPT_DIR/.env" | cut -d'"' -f2)
fi
exec sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(setf *debugger-hook* (lambda (c h) (declare (ignore h)) (format *error-output* "FATAL LISP ERROR: ~a~%" c) (uiop:print-backtrace :stream *error-output*) (uiop:quit 1)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval '(format t "--- Quickloading OpenCortex ---~%")' --eval "(ql:quickload '(:opencortex :croatoan))" --eval '(opencortex:main)'
;;
tui)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
echo -e "Launching Croatoan TUI..."
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
exec sbcl --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval '(ql:quickload :opencortex/tui)' --eval '(opencortex.tui:main)'
;;
cli)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
if command_exists socat; then
echo -e "Connected to OpenCortex on $TARGET_HOST:$TARGET_PORT (Channel: CLI)"
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
if [ "$MESSAGE" = "/exit" ]; then break; fi
# Frame the message
PAYLOAD="(:TYPE :EVENT :META (:SOURCE :CLI) :PAYLOAD (:SENSOR :USER-INPUT :TEXT \"$MESSAGE\"))"
LEN=$(printf "%s" "$PAYLOAD" | wc -c)
HEXLEN=$(printf "%06x" $LEN)
# Send and read response
(printf "%s%s" "$HEXLEN" "$PAYLOAD" | nc -N $TARGET_HOST $TARGET_PORT) | while read -r LINE; do
CLEAN=$(echo "$LINE" | sed 's/^......//')
if [[ "$CLEAN" == *":TEXT"* ]]; then
TEXT=$(echo "$CLEAN" | sed -n 's/.*:TEXT "\([^"]*\)".*/\1/p')
echo -e "Agent: $TEXT"
fi
done
done
else
echo "Error: socat required for CLI interaction."
exit 1
fi
;;
*)
echo -e "Unknown command: $COMMAND"
echo "Available commands: setup, boot, tui, cli"
exit 1
;;
esac
#+end_src
** Metabolic Docker Infrastructure (Dockerfile)
#+begin_src dockerfile :tangle ../infrastructure/docker/Dockerfile
FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
sbcl \
emacs-nox \
curl \
git \
socat \
netcat-openbsd \
libssl-dev \
libncurses5-dev \
libffi-dev \
zlib1g-dev \
libsqlite3-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Quicklisp
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp \
&& sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))" \
&& rm quicklisp.lisp
WORKDIR /app
COPY . .
# Initialize system in non-interactive mode
RUN mkdir -p /root/memex && ./opencortex.sh setup --non-interactive
EXPOSE 9105
CMD ["./opencortex.sh", "boot"]
#+end_src

View File

@@ -6,71 +6,38 @@
* The Skill Engine (skills.lisp)
** Architectural Intent: Late-Binding Intelligence
A static, hardcoded architecture is inherently fragile. To build a autonomous agent that can evolve alongside its user, the harness must be a "Thin Shell" that delegates its capabilities to dynamic, hot-reloadable modules known as **Skills**. This is the core of our **Thin Harness / Thick Skill Microkernel Architecture**.
A static, hardcoded architecture is inherently fragile. The ~opencortex~ Skill Engine enables **Late-Binding Intelligence**, allowing the system to discover and integrate new cognitive capabilities (actuators, solvers, sensors) at runtime without a kernel restart.
Skills unify the **"Why"** (Literate Org documentation) and the **"How"** (Functional Lisp implementation). This allows the harness to "learn" new behaviors without a full system restart, enabling a continuous evolutionary loop where the agent can eventually inspect and improve its own code.
** Global Skill Registry
*** The True Microkernel (Decoupled Core Skills)
Historically, "core" skills (like State Persistence or Gateways) were statically compiled into the harness for performance. We have fundamentally decoupled this. Now, *all* behavioral skills are pure user-space dynamic modules.
**** MANDATE: Dynamic Loading (No Tangling)
Skills are defined as single-file Literate Org notes. Unlike the core harness, **Skills MUST NOT use :tangle headers.**
Instead of being compiled into static source files, skills are:
1. **Discovered:** The harness scans the `skills/` directory for `.org` files.
2. **Parsed:** The harness extracts Lisp code blocks directly from the Org AST.
3. **Jailed:** Each skill is evaluated inside its own temporary, isolated package namespace.
4. **Hot-Loaded:** Skills can be added, modified, or removed at runtime without restarting the Lisp image.
This ensures the agent can safely read, write, and repair its own capabilities without breaking the physical file structure. If a user wishes to swap the IPFS persistence skill for an AWS S3 one, they simply swap the `.org` file; no kernel recompilation is required.
*** Dormant Verification (Tests)
Because skills are no longer statically compiled into the core `opencortex` ASDF system, their associated `FiveAM` test blocks are currently dormant during a standard static build. The tests still exist within the literate `.org` files as verifiable documentation, but executing them requires either dynamic evaluation at runtime or a dedicated test-loader skill.
*** 1. The Package Jailing Principle
Every skill is evaluated within its own dedicated Common Lisp package (namespace). This "Jailing" prevents symbol collisions between disparate skills and ensures that a bug in one module cannot easily corrupt the internal state of another.
*** 2. Deterministic Load Ordering
Skills often depend on one another. The harness implements a deterministic topological sorting algorithm to ensure that dependencies are loaded before the skills that require them.
** Skill Architecture
#+begin_src mermaid
flowchart TD
Registry[Skills Registry] --> S1[Skill: System Policy]
Registry --> S2[Skill: LLM Gateway]
Registry --> S3[Skill: Token Accountant]
S2 -- Depends On --> S1
S3 -- Depends On --> S2
subgraph Jailing[Package Jailing]
P1[Package: OPENCORTEX.SKILLS.S1]
P2[Package: OPENCORTEX.SKILLS.S2]
P3[Package: OPENCORTEX.SKILLS.S3]
end
S1 --> P1
S2 --> P2
S3 --> P3
#+end_src
** Package Context
We begin by ensuring we are in the correct isolated harness namespace.
#+begin_src lisp :tangle ../src/skills.lisp
#+begin_src lisp :tangle ../library/skills.lisp
(in-package :opencortex)
#+end_src
** Skill Metadata (defstruct skill)
The core data structure representing an agent capability. It includes the trigger condition, the probabilistic prompt generator, and the deterministic safety gate.
(defun COSINE-SIMILARITY (v1 v2)
"Computes the cosine similarity between two vectors.
Both arguments should be sequences of numbers. Returns a value between -1.0 and 1.0."
(let ((len1 (length v1)) (len2 (length v2)))
(if (or (zerop len1) (zerop len2))
0.0
(let ((dot-product 0.0d0)
(norm1 0.0d0)
(norm2 0.0d0))
(let ((len (min len1 len2)))
(dotimes (i len)
(let ((x (coerce (elt v1 i) 'double-float)))
(let ((y (coerce (elt v2 i) 'double-float)))
(incf dot-product (* x y))
(incf norm1 (* x x))
(incf norm2 (* y y))))))
(if (or (zerop norm1) (zerop norm2))
0.0
(/ dot-product (sqrt (* norm1 norm2))))))))
(defun VAULT-MASK-STRING (s) "[MASKED]") ; Stub
(defvar *VAULT-MEMORY* (make-hash-table :test 'equal))
#+begin_src lisp :tangle ../src/skills.lisp
(defstruct skill name priority dependencies trigger-fn probabilistic-prompt deterministic-fn)
#+end_src
** Skill Catalog Tracking
The harness maintains a stateful tracking table for all skill files discovered in the environment (~notes/org-skill-*.org~).
#+begin_src lisp :tangle ../src/skills.lisp
(defvar *skill-catalog* (make-hash-table :test 'equal)
"A stateful tracking table for all skill files discovered in the environment.")
@@ -79,27 +46,18 @@ The harness maintains a stateful tracking table for all skill files discovered i
(status :discovered) ;; :discovered, :loading, :ready, :failed
error-log
(load-time 0))
#+end_src
** Skill Selection (find-triggered-skill)
The primary dispatcher for the Probabilistic Engine. It iterates through the registry to find the highest-priority skill whose trigger function matches the current cognitive context.
#+begin_src lisp :tangle ../src/skills.lisp
(defun find-triggered-skill (context)
"Returns the highest priority skill whose trigger condition matches the context."
"Returns the highest priority skill whose trigger matches context AND has a probabilistic prompt."
(let ((triggered nil))
(maphash (lambda (name skill)
(declare (ignore name))
(when (ignore-errors (funcall (skill-trigger-fn skill) context))
(when (and (skill-probabilistic-prompt skill)
(ignore-errors (funcall (skill-trigger-fn skill) context)))
(push skill triggered)))
*skills-registry*)
(first (sort triggered #'> :key #'skill-priority))))
#+end_src
** Skill Definition Macro (defskill)
The interface used within Org files to register new capabilities. Note that dependencies are explicitly quoted to prevent premature evaluation during the macro expansion phase.
#+begin_src lisp :tangle ../src/skills.lisp
(defmacro defskill (name &key priority dependencies trigger probabilistic deterministic)
"Registers a new skill into the global registry."
`(setf (gethash (string-downcase (string ,name)) *skills-registry*)
@@ -109,12 +67,7 @@ The interface used within Org files to register new capabilities. Note that depe
:trigger-fn ,trigger
:probabilistic-prompt ,probabilistic
:deterministic-fn ,deterministic)))
#+end_src
** Dependency Resolution (resolve-skill-dependencies)
Recursively flattens the dependency graph for a given skill. This is used during hot-unloading to ensure that dependent skills are also refreshed.
#+begin_src lisp :tangle ../src/skills.lisp
(defun resolve-skill-dependencies (skill-name)
"Recursively resolves dependencies for a given skill name."
(let ((resolved nil) (seen nil))
@@ -130,10 +83,8 @@ Recursively flattens the dependency graph for a given skill. This is used during
(nreverse resolved))))
#+end_src
** Metadata Parsing (parse-skill-metadata)
A robust, low-level scanner that extracts `#+DEPENDS_ON:` and `:ID:` tags from an Org file. This allows the harness to calculate the load order without needing to parse the full Org AST, which would create a boot-time circularity.
#+begin_src lisp :tangle ../src/skills.lisp
** Skill File Analysis (parse-skill-metadata)
#+begin_src lisp :tangle ../library/skills.lisp
(defun parse-skill-metadata (filepath)
"Extracts ID and DEPENDS_ON tags using robust regex scanning."
(let ((dependencies nil)
@@ -151,15 +102,8 @@ A robust, low-level scanner that extracts `#+DEPENDS_ON:` and `:ID:` tags from a
(values id (remove-if (lambda (s) (= 0 (length s))) dependencies))))
#+end_src
** Topological Sorting (topological-sort-skills)
This is the core algorithm of the boot sequence. It uses a **Depth-First Search (DFS)** to resolve the skill dependency graph.
It performs three critical roles:
1. **Load Ordering:** Ensures that "foundational" skills (like the LLM Gateway) are loaded before high-level "behavioral" skills.
2. **ID Resolution:** Correct maps Org `:ID:` properties to filepaths.
3. **Cycle Detection:** It uses a recursion stack to detect circular dependencies (e.g., A depends on B, B depends on A) and errors out safely before the Lisp image hangs.
#+begin_src lisp :tangle ../src/skills.lisp
** Dependency Resolution (topological-sort-skills)
#+begin_src lisp :tangle ../library/skills.lisp
(defun topological-sort-skills (skills-dir)
"Returns a list of skill filepaths sorted by dependency (dependencies first)."
(let ((files (uiop:directory-files skills-dir "org-skill-*.org"))
@@ -202,31 +146,29 @@ It performs three critical roles:
(nreverse result))))
#+end_src
** Syntax Validation (validate-lisp-syntax)
A pre-flight safety check. Before evaluating any code from an Org file, the harness attempts to ~read~ the entire string. If the reader encounters a syntax error (like an unclosed parenthesis), the load is aborted before the Lisp image can crash.
#+begin_src lisp :tangle ../src/skills.lisp
(defun validate-lisp-syntax (code-string)
"Checks if a string contains valid, readable Common Lisp forms."
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof) until (eq form :eof))
(values t nil)))
(error (c) (values nil (format nil "~a" c)))))
#+end_src
** Jailed Loading (load-skill-from-org)
The core "hot-loading" mechanism. It extracts Lisp blocks from an Org file and evaluates them within a "Jail" (an isolated package).
#+begin_src lisp :tangle ../library/skills.lisp
(defun validate-lisp-syntax (code-string)
"Checks if a string contains valid, readable Common Lisp forms.
Delegates to the Lisp Validator skill when available; falls back to a basic
reader check during early boot before the validator skill is loaded."
(let ((result
(if (fboundp 'lisp-validator-validate)
(lisp-validator-validate code-string :strict nil)
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof) until (eq form :eof)))
(list :status :success))
(error (c)
(list :status :error :reason (format nil "~a" c)))))))
(if (eq (getf result :status) :success)
(values t nil)
(values nil (or (getf result :reason) "Lisp Validator rejected code.")))))
*** The Jailing Algorithm:
1. **Isolation:** It generates a package name based on the filename (e.g., ~OPENCORTEX.SKILLS.CORE-LOGIC~).
2. **Namespace Protection:** It inherits external symbols from the ~OPENCORTEX~ package, allowing the skill to use the harness API, but keeps its internal helper functions local.
3. **Block Filtering:** It explicitly ignores blocks that contain ~:tangle~, ensuring that harness-level code (which is already in ~src/~) is not accidentally re-evaluated as skill logic.
#+begin_src lisp :tangle ../src/skills.lisp
(defun load-skill-from-org (filepath)
"Parses and evaluates Lisp blocks from an Org file into a jailed package."
"Parses and evaluates Lisp blocks with :tangle directives from an Org file.
Only loads blocks that specify a .lisp tangle target, ignoring tests and examples."
(let* ((skill-base-name (pathname-name filepath))
(entry (or (gethash skill-base-name *skill-catalog*) (make-skill-entry :filename skill-base-name))))
(setf (skill-entry-status entry) :loading)
@@ -236,39 +178,42 @@ The core "hot-loading" mechanism. It extracts Lisp blocks from an Org file and e
(let* ((content (uiop:read-file-string filepath))
(lines (uiop:split-string content :separator '(#\Newline)))
(in-lisp-block nil)
(collect-this-block nil)
(lisp-code "")
(pkg-name (intern (string-upcase (format nil "OPENCORTEX.SKILLS.~a" skill-base-name)) :keyword)))
(dolist (line lines)
(let ((clean-line (string-trim '(#\Space #\Tab #\Return) line)))
(cond ((uiop:string-prefix-p "#+begin_src lisp" (string-downcase clean-line))
;; Only load blocks that are NOT tangled to src/ or elsewhere
(if (search ":tangle" (string-downcase clean-line))
(setf in-lisp-block nil)
(setf in-lisp-block t)))
(setf in-lisp-block t)
;; Only collect blocks with a :tangle directive pointing to a
;; runtime .lisp file (exclude tests and :tangle no)
(let ((tl (string-downcase clean-line)))
(setf collect-this-block
(and (search ":tangle" tl)
(not (search ":tangle no" tl))
(search ".lisp" tl)
(not (search "tests/" tl))
(not (search "test/" tl))))))
((uiop:string-prefix-p "#+end_src" (string-downcase clean-line))
(setf in-lisp-block nil))
(in-lisp-block
(setf in-lisp-block nil)
(setf collect-this-block nil))
((and in-lisp-block collect-this-block)
(unless (or (uiop:string-prefix-p ":PROPERTIES:" (string-upcase clean-line))
(uiop:string-prefix-p ":END:" (string-upcase clean-line)))
(setf lisp-code (concatenate 'string lisp-code line (string #\Newline))))))))
(if (= (length lisp-code) 0)
(progn (setf (skill-entry-status entry) :ready) t) ;; Valid empty skill
(progn (setf (skill-entry-status entry) :ready) t)
(progn
;; PRE-FLIGHT: Syntax Validation
(multiple-value-bind (valid-p err) (validate-lisp-syntax lisp-code)
(unless valid-p
(error "Syntax Error: ~a" err)))
(unless valid-p (error "Syntax Error: ~a" err)))
(harness-log "HARNESS: Jailing skill '~a' in package ~a" skill-base-name pkg-name)
(unless (find-package pkg-name)
(let ((new-pkg (make-package pkg-name :use '(:cl))))
(do-external-symbols (sym (find-package :opencortex)) (shadowing-import sym new-pkg))))
(use-package :opencortex new-pkg)))
(let ((*read-eval* nil) (*package* (find-package pkg-name)))
(eval (read-from-string (format nil "(progn ~a)" lisp-code))))
(setf (skill-entry-status entry) :ready)
t)))
(error (c)
@@ -277,12 +222,7 @@ The core "hot-loading" mechanism. It extracts Lisp blocks from an Org file and e
(setf (skill-entry-status entry) :failed)
(setf (skill-entry-error-log entry) msg)
nil)))))
#+end_src
** Safe Loading with Timeout (load-skill-with-timeout)
Wraps the skill loader in a thread with a hard timeout to prevent a single malformed skill from hanging the entire boot sequence.
#+begin_src lisp :tangle ../src/skills.lisp
(defun load-skill-with-timeout (filepath timeout-seconds)
"Loads a skill Org file with a hard execution timeout."
(let* ((finished nil)
@@ -306,13 +246,7 @@ Wraps the skill loader in a thread with a hard timeout to prevent a single malfo
#+end_src
** Initializing All Skills (initialize-all-skills)
The `initialize-all-skills` function is the unified orchestrator for the system boot sequence. It enforces the **Verification Lock**:
1. **Mandatory Check:** It reads the `MANDATORY_SKILLS` environment variable and ensures every required skill exists in the source directory.
2. **Topological Boot:** It resolves inter-skill dependencies to ensure policies and actuators are loaded in the correct order.
3. **Timed Loading:** Every skill is loaded with a 5-second timeout.
4. **Boot Halt:** If a *mandatory* skill fails to load (e.g., due to a syntax error), the entire system halts with a `BOOT FAILURE` to prevent an unaligned or unsecure state.
#+begin_src lisp :tangle ../src/skills.lisp
#+begin_src lisp :tangle ../library/skills.lisp
(defun initialize-all-skills ()
"Scans the directory defined by SKILLS_DIR and hot-loads skills using topological order."
(let* ((env-path (uiop:getenv "SKILLS_DIR"))
@@ -325,15 +259,14 @@ The `initialize-all-skills` function is the unified orchestrator for the system
(return-from initialize-all-skills nil))
(let ((sorted-files (topological-sort-skills skills-dir)))
;; MANDATE: Configurable mandatory skills must be present for a safe boot
(let* ((mandatory-env (uiop:getenv "MANDATORY_SKILLS"))
(mandatory-skills (if mandatory-env
(mapcar (lambda (s) (string-trim '(#\Space) s))
(uiop:split-string mandatory-env :separator '(#\,)))
(mapcar (lambda (s) (string-trim '(#\Space #\" #\') s))
(uiop:split-string mandatory-env :separator '( #\,)))
'("org-skill-policy" "org-skill-bouncer"))))
(dolist (req mandatory-skills)
(unless (member req sorted-files :key #'pathname-name :test #'string-equal)
(error "BOOT FAILURE: Mandatory skill '~a' not found in skills directory." req)))
(error "BOOT FAILURE: Mandatory skill '~a' not found in skills directory: ~a" req (uiop:native-namestring skills-dir))))
(harness-log "==================================================")
(harness-log " LOADER: Initializing ~a skills..." (length sorted-files))
@@ -348,7 +281,6 @@ The `initialize-all-skills` function is the unified orchestrator for the system
(error "BOOT FAILURE: Mandatory skill '~a' failed to load (Status: ~a)." skill-name status)
(harness-log "LOADER WARNING: Skill '~a' failed to load." skill-name))))))
;; Final Summary
(let ((ready 0) (failed 0))
(maphash (lambda (k v)
(declare (ignore k))
@@ -360,23 +292,7 @@ The `initialize-all-skills` function is the unified orchestrator for the system
#+end_src
** Toolbelt Prompt Generation (generate-tool-belt-prompt)
Every cognitive tool registered by a skill is automatically documented and injected into the LLM system prompt. This ensures that the agent is always aware of its current physical capabilities.
#+begin_src mermaid
flowchart LR
Registry[(Tool Registry)] --> Generator[Prompt Generator]
Generator --> Prompt[Final System Prompt]
subgraph Actuators
ToolA[Shell]
ToolB[Emacs]
ToolC[Grep]
end
ToolA --> Registry
ToolB --> Registry
ToolC --> Registry
#+end_src
#+begin_src lisp :tangle ../src/skills.lisp
#+begin_src lisp :tangle ../library/skills.lisp
(defun generate-tool-belt-prompt ()
"Aggregates all registered cognitive tools into a descriptive prompt."
(let ((output (format nil "AVAILABLE TOOLS:
@@ -388,7 +304,7 @@ EXAMPLES:
(:target :tool :action :call :tool \"shell\" :args (:cmd \"ls -la\"))
---
")))
" )))
(maphash (lambda (name tool)
(setf output (concatenate 'string output
(format nil "- ~a: ~a~% Parameters: ~s~%~%"
@@ -400,10 +316,8 @@ EXAMPLES:
#+end_src
** The Default Tool Belt
The harness provides a baseline set of cognitive tools that enable core system interaction.
*** The Eval Tool (Internal Inspection)
#+begin_src lisp :tangle ../src/skills.lisp
#+begin_src lisp :tangle ../library/skills.lisp
(def-cognitive-tool :eval "Evaluates raw Common Lisp code in the harness image. Use this for complex calculations or internal state inspection."
((:code :type :string :description "The Lisp code to evaluate"))
:guard (lambda (args context)
@@ -421,7 +335,7 @@ The harness provides a baseline set of cognitive tools that enable core system i
#+end_src
*** The Grep Tool (File Discovery)
#+begin_src lisp :tangle ../src/skills.lisp
#+begin_src lisp :tangle ../library/skills.lisp
(def-cognitive-tool :grep-search "Searches for a pattern in the project files."
((:pattern :type :string :description "The regex pattern to search for")
(:dir :type :string :description "Directory to search in (default is project root)"))
@@ -433,7 +347,7 @@ The harness provides a baseline set of cognitive tools that enable core system i
#+end_src
*** The Shell Tool (Machine Actuation)
#+begin_src lisp :tangle ../src/skills.lisp
#+begin_src lisp :tangle ../library/skills.lisp
(def-cognitive-tool :shell "Executes a shell command on the local machine. Use this for file operations, system checks, or running tests."
((:cmd :type :string :description "The full bash command to execute"))
:guard (lambda (args context)

174
harness/tui-client.org Normal file
View File

@@ -0,0 +1,174 @@
:PROPERTIES:
:ID: tui-client-spec
:CREATED: [2026-04-17 Fri 11:00]
:END:
#+TITLE: OpenCortex TUI Client (Standalone)
#+STARTUP: content
#+FILETAGS: :tui:ux:client:
* Overview
The OpenCortex TUI Client is a standalone Common Lisp application built on **Croatoan**. It provides a real-time, multi-window interface for interacting with the OpenCortex daemon.
* Implementation
#+begin_src lisp :tangle ../library/tui-client.lisp
(in-package :cl-user)
(defpackage :opencortex.tui
(:use :cl :croatoan)
(:export :main))
(in-package :opencortex.tui)
(defvar *daemon-host* "127.0.0.1")
(defvar *daemon-port* 9105)
(defvar *socket* nil)
(defvar *stream* nil)
(defvar *chat-history* (list))
(defvar *status-text* "Connecting...")
(defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t))
(defvar *is-running* t)
(defvar *queue-lock* (bt:make-lock))
(defvar *incoming-msgs* nil)
(defun enqueue-msg (msg)
(bt:with-lock-held (*queue-lock*)
(push msg *incoming-msgs*)))
(defun dequeue-msgs ()
(bt:with-lock-held (*queue-lock*)
(let ((msgs (nreverse *incoming-msgs*)))
(setf *incoming-msgs* nil)
msgs)))
(defun clean-keywords (msg)
(if (listp msg)
(let ((clean nil))
(loop for (k v) on msg by #'cddr
do (push (intern (string k) :keyword) clean)
(push v clean))
(nreverse clean))
msg))
(defun format-payload (payload)
"Extracts human-readable text from a protocol payload, handling nested tool calls."
(let* ((action (getf payload :ACTION))
(text (getf payload :TEXT))
(msg (getf payload :MESSAGE))
(tool (getf payload :TOOL))
(prompt (getf payload :PROMPT))
(args (getf payload :ARGS))
(result (getf payload :RESULT)))
(cond (text text)
(msg msg)
((eq action :MESSAGE) (getf payload :TEXT))
((and tool prompt) (format nil "THOUGHT [~a]: ~a" tool prompt))
((and tool args)
(let ((inner-prompt (or (getf args :PROMPT) (getf args :TEXT))))
(if inner-prompt
(format nil "THOUGHT [~a]: ~a" tool inner-prompt)
(format nil "CALL [~a] (ARGS: ~s)" tool args))))
(result (format nil "RESULT: ~a" result))
(t (format nil "~s" payload)))))
(defun listen-thread ()
(loop while *is-running* do
(handler-case
(when (and *stream* (open-stream-p *stream*))
(let ((raw-msg (opencortex:read-framed-message *stream*)))
(unless (member raw-msg '(:eof :error))
(let* ((msg (clean-keywords raw-msg))
(type (or (getf msg :TYPE) (getf msg :type)))
(payload (or (getf msg :PAYLOAD) (getf msg :payload))))
(cond ((and (listp msg) (eq type :EVENT))
(let ((action (or (getf payload :ACTION) (getf payload :action)))
(text (or (getf payload :TEXT) (getf payload :text) (getf payload :MESSAGE) (getf payload :message))))
(cond ((eq action :handshake) (setf *status-text* "Ready"))
(text (enqueue-msg (format nil "SYSTEM: ~a" text))))))
((and (listp msg) (eq type :STATUS))
(setf *status-text* (format nil "[Scribe: ~a] [Gardener: ~a]"
(or (getf msg :SCRIBE) (getf msg :scribe))
(or (getf msg :GARDENER) (getf msg :gardener)))))
((and (listp msg) (member type '(:REQUEST :RESPONSE :LOG)))
(let ((formatted (format-payload payload)))
(when formatted (enqueue-msg formatted))))
((and (listp msg) (eq type :EVENT) (eq (getf payload :SENSOR) :TOOL-OUTPUT))
(let ((formatted (format-payload payload)))
(when formatted (enqueue-msg formatted))))
(t (harness-log "TUI: Ignored unknown type ~a" type)))))
(when (eq raw-msg :eof) (setf *is-running* nil))
(when (eq raw-msg :error) (setf *status-text* "Protocol Error"))))
(error (c) (setf *status-text* (format nil "Net Error: ~a" c)) (setf *is-running* nil)))
(sleep 0.05)))
(defun main ()
(handler-case
(setf *socket* (usocket:socket-connect *daemon-host* *daemon-port*))
(error (e) (format t "Error connecting: ~a~%" e) (return-from main)))
(setf *stream* (usocket:socket-stream *socket*))
(bt:make-thread #'listen-thread :name "tui-listener")
(unwind-protect
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible t)
(let* ((h (height scr))
(w (width scr))
(chat-win (make-instance 'window :height (- h 2) :width w :position (list 0 0)))
(status-win (make-instance 'window :height 1 :width w :position (list (- h 2) 0)))
(input-win (make-instance 'window :height 1 :width w :position (list (- h 1) 0)))
(last-status nil))
(setf (function-keys-enabled-p input-win) t)
(setf (input-blocking input-win) nil)
(loop while *is-running* do
;; 1. Handle incoming messages
(let ((new-msgs (dequeue-msgs)))
(when new-msgs
(dolist (msg new-msgs)
(push msg *chat-history*)
(setf *chat-history* (subseq *chat-history* 0 (min (length *chat-history*) 500))))
(clear chat-win)
(let ((line-num 0))
(dolist (m (reverse (subseq *chat-history* 0 (min (length *chat-history*) (- h 3)))))
(add-string chat-win m :y line-num :x 0)
(incf line-num)))
(refresh chat-win)))
;; 2. Render Status Bar ONLY if changed
(unless (equal *status-text* last-status)
(clear status-win)
(add-string status-win *status-text* :attributes '(:reverse))
(refresh status-win)
(setf last-status *status-text*))
;; 3. Handle Keyboard Input
(let* ((event (get-wide-event input-win))
(ch (and event (typep event 'event) (event-key event))))
(when ch
(cond
((or (eq ch #\Newline) (eq ch #\Return))
(let ((cmd (coerce *input-buffer* 'string)))
(setf (fill-pointer *input-buffer*) 0)
(when (> (length cmd) 0)
;; Local Echo
(enqueue-msg (concatenate 'string "> " cmd))
;; Send to Brain
(let ((framed (opencortex:frame-message (list :TYPE :EVENT
:META (list :SOURCE :tui :SESSION-ID "default")
:PAYLOAD (list :SENSOR :user-input :TEXT cmd)))))
(format *stream* "~a" framed)
(finish-output *stream*)))
(when (string= cmd "/exit") (setf *is-running* nil))))
((or (eq ch :backspace) (eq ch #\Backspace) (eq ch #\Rubout) (eq ch #\Del))
(when (> (length *input-buffer*) 0)
(decf (fill-pointer *input-buffer*))))
((characterp ch)
(vector-push-extend ch *input-buffer*))))
(clear input-win)
(add-string input-win (concatenate 'string "> " (coerce *input-buffer* 'string)))
(move input-win 0 (+ 2 (length *input-buffer*)))
(refresh input-win))
(sleep 0.02))))
(setf *is-running* nil)
(when *socket* (usocket:socket-close *socket*))))
#+end_src

View File

@@ -0,0 +1,32 @@
FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
sbcl \
emacs-nox \
curl \
git \
socat \
netcat-openbsd \
libssl-dev \
libncurses5-dev \
libffi-dev \
zlib1g-dev \
libsqlite3-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Quicklisp
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp \
&& sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))" \
&& rm quicklisp.lisp
WORKDIR /app
COPY . .
# Initialize system in non-interactive mode
RUN mkdir -p /root/memex && ./opencortex.sh setup --non-interactive
EXPOSE 9105
CMD ["./opencortex.sh", "boot"]

148
library/act.lisp Normal file
View File

@@ -0,0 +1,148 @@
(in-package :opencortex)
(defvar *default-actuator* :cli)
(defvar *silent-actuators* '(:cli :system-message :emacs))
(defun initialize-actuators ()
"Loads actuator routing defaults from environment variables and registers core harness actuators."
(let ((def (uiop:getenv "DEFAULT_ACTUATOR"))
(silent (uiop:getenv "SILENT_ACTUATORS")))
(when def
(setf *default-actuator* (intern (string-upcase def) "KEYWORD")))
(when silent
(setf *silent-actuators*
(mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) "KEYWORD"))
(str:split "," silent)))))
;; Register core harness actuators
(register-actuator :system #'execute-system-action)
(register-actuator :tool #'execute-tool-action)
(register-actuator :tui (lambda (action context)
(let* ((meta (getf context :meta))
(stream (getf meta :reply-stream)))
(when (and stream (open-stream-p stream))
(format stream "~a" (frame-message action))
(finish-output stream))))))
(defun dispatch-action (action context)
(let ((payload (proto-get action :payload)))
(when (eq (proto-get payload :sensor) :heartbeat)
(return-from dispatch-action nil)))
"Routes an approved action to its registered physical actuator."
(when (and action (listp action))
(let* ((meta (proto-get context :meta))
(source (proto-get meta :source))
(raw-target (or (ignore-errors (getf action :TARGET))
(ignore-errors (getf action :target))
source
*default-actuator*))
(target (intern (string-upcase (string raw-target)) :keyword))
(actuator-fn (gethash target *actuator-registry*)))
;; Ensure outbound action has meta if context had it
(when (and meta (null (getf action :meta)))
(setf (getf action :meta) meta))
(if actuator-fn
(funcall actuator-fn action context)
(harness-log "ACT ERROR: No actuator for ~s (from ~s)" target raw-target)))))
(defun execute-system-action (action context)
"Processes internal harness commands. (ACTUATOR)"
(declare (ignore context))
(let* ((payload (ignore-errors (getf action :payload)))
(cmd (ignore-errors (getf payload :action))))
(case cmd
(:eval (let ((code (getf payload :code)))
(eval (read-from-string code))))
(:create-skill (let* ((filename (getf payload :filename)) (content (getf payload :content))
(skills-dir (merge-pathnames "skills/" (asdf:system-source-directory :opencortex)))
(full-path (merge-pathnames filename skills-dir)))
(with-open-file (out full-path :direction :output :if-exists :supersede) (write-string content out))
(load-skill-from-org full-path)))
(:message (harness-log "ACT [System]: ~a" (getf payload :text)))
(t (harness-log "ACT ERROR [System]: Unknown command ~s" cmd)))))
(defun format-tool-result (tool-name result)
"Intelligently formats a tool result for user display."
(if (listp result)
(let ((status (getf result :status))
(content (getf result :content))
(msg (getf result :message)))
(cond ((and (eq status :success) content) (format nil "~a" content))
((and (eq status :error) msg) (format nil "ERROR [~a]: ~a" tool-name msg))
(t (format nil "TOOL [~a] RESULT: ~s" tool-name result))))
(format nil "TOOL [~a] RESULT: ~a" tool-name result)))
(defun execute-tool-action (action context)
"Executes a registered cognitive tool. (ACTUATOR)"
(let* ((payload (getf action :payload))
(tool-name (getf payload :tool))
(tool-args (getf payload :args))
(depth (getf context :depth 0))
(meta (getf context :meta))
(source (getf meta :source))
(tool (gethash (string-downcase (string tool-name)) *cognitive-tools*)))
(if tool
(handler-case
(let* ((clean-args (if (and (listp tool-args) (listp (car tool-args))) (car tool-args) tool-args))
(result (funcall (cognitive-tool-body tool) clean-args)))
(let ((feedback (list :TYPE :EVENT :DEPTH (1+ depth) :META meta
:PAYLOAD (list :SENSOR :tool-output :RESULT result :TOOL tool-name))))
;; If we have a source, send a status message with the result, formatted for humans
(when source
(dispatch-action (list :TYPE :REQUEST :TARGET source
:PAYLOAD (list :ACTION :MESSAGE :TEXT (format-tool-result tool-name result)))
context))
feedback))
(error (c)
(list :TYPE :EVENT :DEPTH (1+ depth) :META meta
:PAYLOAD (list :SENSOR :tool-error :tool tool-name :message (format nil "~a" c)))))
(list :TYPE :EVENT :DEPTH (1+ depth) :META meta
:PAYLOAD (list :SENSOR :tool-error :message "Tool not found")))))
(defun act-gate (signal)
"Final Stage: Actuation and feedback generation."
(let* ((approved (getf signal :approved-action))
(type (getf signal :type))
(meta (getf signal :meta))
(source (getf meta :source))
(feedback nil)
;; context must keep internal objects for actuators to function
(context signal))
;; 1. Last-Mile Safety Check (The Bouncer & Deterministic Gates)
(when approved
(let* ((original-type (getf approved :type))
(verified (deterministic-verify approved signal)))
(if (and (listp verified)
(member (getf verified :type) '(:LOG :EVENT :log :event))
(not (member original-type '(:LOG :EVENT :log :event))))
(progn
(harness-log "ACT BLOCKED: Action failed last-mile deterministic check.")
(setf (getf signal :approved-action) nil)
(setf approved nil)
(setf feedback verified))
(progn
(setf (getf signal :approved-action) verified)
(setf approved verified)))))
;; 2. Actuation Logic
(case type
(:REQUEST (dispatch-action signal context))
(:LOG (dispatch-action signal context))
(:EVENT
(if approved
(let* ((target (getf approved :target))
(result (dispatch-action approved context)))
;; If the actuator returns a signal (like :tool-output), it becomes the feedback.
;; Otherwise, generate tool-output feedback for non-silent actuators.
(cond ((and (listp result) (member (getf result :type) '(:EVENT :LOG)))
(setf feedback result))
((and result (not (member target *silent-actuators*)))
(setf feedback (list :type :EVENT :depth (1+ (getf signal :depth 0)) :meta meta
:payload (list :sensor :tool-output :result result :tool approved))))))
;; If no approved action but we have a source, this might be a raw event/log stimulus.
(when source
(dispatch-action signal context)))))
(setf (getf signal :status) :acted)
feedback))

View File

@@ -0,0 +1,44 @@
(in-package :opencortex)
(defun validate-communication-protocol-schema (msg)
"Strict structural validation for incoming communication protocol messages."
(unless (listp msg)
(error "Communication Protocol Schema Error: Message must be a property list (got ~s)" (type-of msg)))
(let ((type (let ((raw (proto-get msg :type))) (if (keywordp raw) (intern (string-upcase (string raw)) :keyword) raw))))
(unless (member type '(:REQUEST :EVENT :RESPONSE :LOG :STATUS))
(progn (harness-log "REJECTED MSG: ~s" msg) (error "Communication Protocol Schema Error: Invalid message type '~a'" type)))
(case type
(:REQUEST
;; Allow missing :target if :source is present in :meta, since reason-gate
;; will infer :target from :source downstream. This preserves "equality of
;; clients" — gateways need not duplicate routing logic.
(let ((target (proto-get msg :target))
(source (proto-get (proto-get msg :meta) :source)))
(unless (or target source)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :target and no :source in :meta to infer it"))
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :payload"))))
(:EVENT
(let ((payload (proto-get msg :payload)))
(unless (and payload (listp payload))
(error "Communication Protocol Schema Error: EVENT missing or invalid :payload"))
(unless (or (proto-get payload :action) (proto-get payload :sensor))
(error "Communication Protocol Schema Error: EVENT payload must contain :action or :sensor"))))
(:RESPONSE
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: RESPONSE missing mandatory :payload"))))
t))
(defskill :skill-communication-protocol-validator
:priority 95
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:protocol-received)))
:probabilistic nil
:deterministic (lambda (action ctx)
(declare (ignore ctx))
(validate-communication-protocol-schema action)
action))

View File

@@ -0,0 +1,75 @@
(in-package :opencortex)
(defvar *actuator-registry* (make-hash-table :test 'equalp)
"Global registry mapping target keywords to their physical actuator functions.")
(defun register-actuator (name fn)
"Registers an actuator function. Actuators receive: (ACTION CONTEXT)."
(let ((key (if (keywordp name) name (intern (string-upcase (string name)) :keyword))))
(setf (gethash key *actuator-registry*) fn)))
(defun frame-message (msg-plist)
"Frames a Lisp plist with a 6-character hex length and a newline for stream integrity."
(let* ((*print-pretty* nil)
(*print-circle* nil)
(msg-string (format nil "~s" msg-plist))
(len (length msg-string)))
(format nil "~6,'0x~a~%" len msg-string)))
(defun read-framed-message (stream)
"Reads a hex-length prefixed S-expression from the stream securely. Skips leading whitespace."
(let ((length-buffer (make-string 6)))
(handler-case
(progn
;; 1. Skip leading whitespace (newlines, spaces, etc.)
(loop for char = (peek-char nil stream nil :eof)
while (and (not (eq char :eof)) (member char '(#\Space #\Newline #\Tab #\Return)))
do (read-char stream))
;; 2. Read the 6-char hex length
(let ((count (read-sequence length-buffer stream)))
(cond ((< count 6) :eof)
(t (let ((len (ignore-errors (parse-integer length-buffer :radix 16))))
(if (not len)
(progn
(harness-log "PROTOCOL ERROR: Invalid header ~s. Attempting resync..." length-buffer)
:error)
(let ((msg-buffer (make-string len)))
(read-sequence msg-buffer stream)
(let ((*read-eval* nil)
(*print-pretty* nil))
(handler-case
(let ((msg (read-from-string msg-buffer)))
(validate-communication-protocol-schema msg)
msg)
(error (c)
(harness-log "PROTOCOL PARSE ERROR: ~a in ~s" c msg-buffer)
:error))))))))))
(error (c)
(harness-log "PROTOCOL READ ERROR: ~a" c)
:error))))
(defun make-hello-message (version)
"Constructs the standard HELLO handshake message."
(list :TYPE :EVENT
:PAYLOAD (list :ACTION :handshake
:VERSION version
:CAPABILITIES '(:AUTH :SWANK :ORG-AST))))
(defun sanitize-protocol-message (msg)
"Recursively strips non-serializable objects from a protocol plist."
(if (and msg (listp msg))
(let ((clean nil))
(loop for (k v) on msg by #'cddr
do (unless (member k '(:reply-stream :socket :stream))
(push k clean)
(push (if (listp v) (sanitize-protocol-message v) v) clean)))
(nreverse clean))
msg))
(defun frame-message (msg)
"Serializes a message plist and prefixes it with a 6-character hex length."
(let* ((sanitized (sanitize-protocol-message msg))
(payload (let ((*print-pretty* nil) (*read-eval* nil)) (format nil "~s" sanitized)))
(len (length payload)))
(format nil "~6,'0x~a" len payload)))

View File

@@ -91,15 +91,18 @@
output))
(defun context-resolve-path (path-string)
"Expands all environment variables ($VAR) within a path string."
(if (and (stringp path-string) (search "$" path-string))
(let ((result path-string))
(ppcre:do-register-groups (var-name) ("\\$([A-Za-z0-9_]+)" path-string)
(let ((var-val (uiop:getenv var-name)))
(when var-val
(setf result (ppcre:regex-replace (format nil "\\$~a" var-name) result var-val)))))
result)
path-string))
"Expands environment variables and strips literal quotes from a path string."
(let ((path (if (stringp path-string)
(string-trim '(#\" #\' #\Space) path-string)
path-string)))
(if (and (stringp path) (search "$" path))
(let ((result path))
(ppcre:do-register-groups (var-name) ("\\$([A-Za-z0-9_]+)" path)
(let ((var-val (uiop:getenv var-name)))
(when var-val
(setf result (ppcre:regex-replace (format nil "\\$~a" var-name) result var-val)))))
result)
path)))
(defun context-assemble-global-awareness (&optional signal)
"Produces a high-level skeletal outline of the current Memory for the LLM."

View File

@@ -0,0 +1,109 @@
(in-package :opencortex)
(defun bouncer-scan-secrets (text)
"Returns the name of the secret found in TEXT, or NIL if clean."
(when (and text (stringp text))
(let ((found-secret nil))
(maphash (lambda (key val)
(when (and val (stringp val) (> (length val) 5))
(when (search val text)
(setf found-secret key))))
opencortex::*vault-memory*)
found-secret)))
(defun bouncer-check-network-exfil (cmd)
"Returns T if the command appears to target an unwhitelisted external host."
(when (and cmd (stringp cmd))
;; Basic check for common data exfiltration tools being used with IPs/URLs
(let ((network-whitelist '("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")))
(when (cl-ppcre:scan "(http|https|ftp)://([\\w\\.-]+)" cmd)
(multiple-value-bind (match regs)
(cl-ppcre:scan-to-strings "(http|https|ftp)://([\\w\\.-]+)" cmd)
(declare (ignore match))
(let ((domain (aref regs 1)))
(not (some (lambda (safe) (search safe domain)) network-whitelist))))))))
(defun bouncer-check (action context)
"The 5-Vector security gate. Blocks or queues actions based on risk."
(let* ((target (getf action :target))
(payload (getf action :payload))
(text (or (getf payload :text) (getf action :text)))
;; Extract cmd from direct shell or tool-mediated shell call
(cmd (or (getf payload :cmd)
(when (and (eq target :tool) (equal (getf payload :tool) "shell"))
(getf (getf payload :args) :cmd))))
(approved (getf action :approved)))
(cond
;; 0. Bypass for already approved actions
(approved action)
;; 1. Secret Exposure Vector (Hard Block)
((and text (bouncer-scan-secrets text))
(let ((secret-name (bouncer-scan-secrets text)))
(harness-log "SECURITY VIOLATION: Blocked leak of secret ~a" secret-name)
`(:type :log :payload (:level :error :text ,(format nil "Action blocked: Potential exposure of ~a" secret-name)))))
;; 2. Network Exfiltration Vector (Authorization Required)
((and (or (eq target :shell)
(and (eq target :tool) (equal (getf payload :tool) "shell")))
(bouncer-check-network-exfil cmd))
(harness-log "SECURITY WARNING: External network call detected. Queuing for approval.")
`(:type :EVENT :payload (:sensor :approval-required :action ,action)))
;; 3. High-Impact Target Vector (Authorization Required)
((or (member target '(:shell))
(and (eq target :tool) (member (getf payload :tool) '("shell" "repair-file") :test #'string=))
(and (eq target :EMACS) (eq (getf payload :action) :eval)))
(harness-log "SECURITY: High-impact action ~a requires approval." (or (getf payload :tool) target))
`(:type :EVENT :payload (:sensor :approval-required :action ,action)))
;; 4. Default Pass
(t action))))
(defun bouncer-process-approvals ()
"Scans the object store for APPROVED flight plans and re-injects their actions."
(let ((approved-nodes (list-objects-with-attribute :TODO "APPROVED"))
(found-any nil))
(dolist (node approved-nodes)
(let* ((tags (getf (org-object-attributes node) :TAGS))
(action-str (getf (org-object-attributes node) :ACTION)))
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
(harness-log "BOUNCER: Found approved flight plan ~a. Re-injecting..." (org-object-id node))
(let ((action (ignore-errors (read-from-string action-str))))
(when action
;; Mark as approved to bypass the gate
(setf (getf action :approved) t)
(inject-stimulus action)
;; Mark as DONE
(setf (getf (org-object-attributes node) :TODO) "DONE")
(setq found-any t))))))
found-any))
(defun bouncer-deterministic-gate (action context)
"Main gate for the bouncer skill."
(let* ((payload (getf context :payload))
(sensor (getf payload :sensor)))
(case sensor
(:approval-required
(let* ((blocked-action (getf payload :action))
(id (org-id-new)))
(harness-log "BOUNCER: Creating flight plan node...")
;; Create the node in Emacs (or inbox)
(list :type :REQUEST :target :EMACS :action :insert-node
:id id :attributes `(:TITLE "Flight Plan: High-Risk Action"
:TODO "PLAN"
:TAGS ("FLIGHT_PLAN")
:ACTION ,(format nil "~s" blocked-action)))))
(:heartbeat
;; Periodically check for approvals
(bouncer-process-approvals)
(if action (bouncer-check action context) action))
(otherwise
(if action (bouncer-check action context) action)))))
(defskill :skill-bouncer
:priority 150
:trigger (lambda (ctx) t) ;; Bouncer evaluates all actions deterministically
:probabilistic nil
:deterministic #'bouncer-deterministic-gate)

View File

@@ -0,0 +1,81 @@
(defvar *cli-port* 9105)
(defvar *cli-server-socket* nil)
(defvar *cli-server-thread* nil)
(defun execute-cli-action (action context)
"Sends a framed message back to the connected CLI client."
(let* ((payload (proto-get action :PAYLOAD))
(meta (getf context :meta))
(stream (getf meta :reply-stream)))
(handler-case
(if (and stream (open-stream-p stream))
(progn
(format stream "~a" (frame-message action))
(finish-output stream)
(format stream "~a" (frame-message '(:TYPE :STATUS :SCRIBE :IDLE :GARDENER :SLEEPING)))
(finish-output stream))
(harness-log "CLI ERROR: No active or open reply stream for signal."))
(error (c) (harness-log "CLI ACTUATOR ERROR: ~a" c)))))
(defun handle-cli-slash-command (cmd stream)
(cond
((string= cmd "/exit") (return-from handle-cli-slash-command :exit))
(t (format stream "~a" (frame-message (list :TYPE :REQUEST :PAYLOAD (list :ACTION :MESSAGE :TEXT (format nil "Unknown command: ~a" cmd))))))))
(defun handle-cli-client (stream)
"Reads framed messages from a CLI client and injects them as stimuli."
(harness-log "CLI: Client connected.")
(handler-case
(progn
;; 1. Send Handshake
(format stream "~a" (frame-message (make-hello-message "0.1.0")))
(finish-output stream)
(format stream "~a" (frame-message '(:TYPE :STATUS :SCRIBE :IDLE :GARDENER :SLEEPING)))
(finish-output stream)
;; 2. Communication Loop
(loop
(let ((msg (read-framed-message stream)))
(cond ((eq msg :eof) (return))
((eq msg :error) (return))
(t (let* ((payload (proto-get msg :payload))
(text (proto-get payload :text))
(meta (proto-get msg :meta)))
(if (and text (stringp text) (char= (char text 0) #\/))
(when (eq (handle-cli-slash-command text stream) :exit) (return))
(progn
;; Default meta if missing
(unless meta
(setf (getf msg :meta) (list :SOURCE :CLI :SESSION-ID "default")))
(harness-log "CLI: Received input -> ~s" msg)
(inject-stimulus msg :stream stream)))))))))
(error (c) (harness-log "CLI CLIENT DISCONNECT: ~a" c)))
(harness-log "CLI: Client disconnected."))
(defun start-cli-gateway (&optional (port *cli-port*))
"Starts the TCP listener for local CLI clients."
(setf *cli-server-socket* (usocket:socket-listen "0.0.0.0" port :reuse-address t))
(setf *cli-server-thread*
(bt:make-thread
(lambda ()
(unwind-protect
(loop
(let* ((socket (usocket:socket-accept *cli-server-socket*))
(stream (usocket:socket-stream socket)))
(bt:make-thread (lambda ()
(unwind-protect (handle-cli-client stream)
(usocket:socket-close socket)))
:name "opencortex-cli-client-handler")))
(usocket:socket-close *cli-server-socket*)))
:name "opencortex-cli-gateway"))
(harness-log "CLI: Gateway listening on port ~a" port))
(register-actuator :CLI #'execute-cli-action)
(defskill :skill-gateway-cli
:priority 200
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))
(start-cli-gateway)

View File

@@ -0,0 +1,82 @@
(defun vault-get-secret (provider &key type)
"Retrieves a secret (api-key or session) for a provider.")
(defun vault-set-secret (provider secret &key type)
"Securely stores a secret and triggers a Merkle snapshot.")
(defvar opencortex::*vault-memory* (make-hash-table :test 'equal)
"In-memory cache of sensitive credentials.")
(defun vault-mask-string (str)
"Returns a masked version of a sensitive string."
(if (and str (> (length str) 8))
(format nil "~a...~a" (subseq str 0 4) (subseq str (- (length str) 4)))
"[REDACTED]"))
(defun vault-get-secret (provider &key (type :api-key))
"Retrieves a credential. Type can be :api-key or :session."
(let* ((key (format nil "~a-~a" provider type))
(val (gethash key opencortex::*vault-memory*)))
(if val
val
;; Fallback to environment
(let ((env-var (case provider
((:gemini :gemini-api) "GEMINI_API_KEY")
(:openai "OPENAI_API_KEY")
(:anthropic "ANTHROPIC_API_KEY")
(:groq "GROQ_API_KEY")
(:openrouter "OPENROUTER_API_KEY")
(:telegram "TELEGRAM_BOT_TOKEN")
(:signal "SIGNAL_ACCOUNT_NUMBER")
(:matrix-homeserver "MATRIX_HOMESERVER")
(:matrix-token "MATRIX_ACCESS_TOKEN")
(t nil))))
(when (and env-var (eq type :api-key))
(uiop:getenv env-var))))))
(defun vault-set-secret (provider secret &key (type :api-key))
"Securely stores a secret and triggers a Merkle snapshot."
(let ((key (format nil "~a-~a" provider type)))
(setf (gethash key opencortex::*vault-memory*) secret)
(harness-log "VAULT - Updated ~a for ~a. Triggering Merkle snapshot..." type provider)
(snapshot-memory)
t))
(defun vault-onboard-gemini-web ()
"Instructions for the Autonomous Cookie Handshake."
(harness-log "--- GEMINI WEB ONBOARDING ---")
(harness-log "1. Visit gemini.google.com")
(harness-log "2. Run the 'Get Gemini Cookies' Bookmarklet.")
(harness-log " CODE: javascript:(function(){const c=document.cookie.split('; ').reduce((r,v)=>{const [n,val]=v.split('=');r[n]=val;return r},{});const target=['__Secure-1PSID','__Secure-1PSIDTS'];const out=target.map(n=>({name:n,value:c[n]}));prompt('Copy JSON:',JSON.stringify(out));})();")
(harness-log "PLATFORM GUIDE: Chrome/Firefox/Safari all support Bookmarklets via 'Add Page' or 'New Bookmark'.")
t)
(progn
(defskill :skill-credentials-vault
:priority 200 ; High priority, foundational
:trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :onboarding-request))
:probabilistic nil
:deterministic (lambda (action ctx)
(vault-onboard-gemini-web)
action)))
#|
(defpackage :opencortex-vault-tests
(:use :cl :fiveam :opencortex))
(in-package :opencortex-vault-tests)
(def-suite vault-suite :description "Tests for the Credentials Vault.")
(in-suite vault-suite)
(test test-masking
(is (equal "sk-t...-key" (opencortex::vault-mask-string "sk-test-key")))
(is (equal "[REDACTED]" (opencortex::vault-mask-string "short"))))
(test test-vault-persistence
"Verify that setting a secret triggers a snapshot (mock check)."
(let ((old-version (opencortex::org-object-version (gethash "root" *memory*))))
(opencortex:vault-set-secret :test "secret-val")
(is (> (opencortex::org-object-version (gethash "root" *memory*)) old-version))))
|#

View File

@@ -0,0 +1,68 @@
(in-package :opencortex)
(defvar *gardener-last-audit* 0
"The universal-time of the last full Memex audit.")
(defun gardener-find-broken-links ()
"Returns a list of broken ID links found in the Memex."
(let ((broken nil))
(maphash (lambda (id obj)
(let ((content (org-object-content obj)))
(when content
(cl-ppcre:do-register-groups (target-id) ("id:([A-Za-z0-9-]+)" content)
(unless (lookup-object target-id)
(push (list :source id :broken-target target-id) broken))))))
*memory*)
broken))
(defun gardener-find-orphans ()
"Returns a list of IDs for headlines that are structurally isolated."
(let ((inbound (make-hash-table :test 'equal))
(outbound (make-hash-table :test 'equal))
(orphans nil))
;; 1. Map all connections
(maphash (lambda (id obj)
(let ((content (org-object-content obj)))
(when content
(cl-ppcre:do-register-groups (target-id) ("id:([A-Za-z0-9-]+)" content)
(setf (gethash id outbound) t)
(setf (gethash target-id inbound) t)))))
*memory*)
;; 2. Identify nodes with zero connections
(maphash (lambda (id obj)
(declare (ignore obj))
(unless (or (gethash id inbound) (gethash id outbound))
(push id orphans)))
*memory*)
orphans))
(defun gardener-deterministic-gate (action context)
"Main gate for the Gardener skill. Audits graph integrity."
(declare (ignore action context))
(let ((broken (gardener-find-broken-links))
(orphans (gardener-find-orphans)))
(when (or broken orphans)
(harness-log "GARDENER: Audit found ~a broken links and ~a orphans."
(length broken) (length orphans))
(dolist (link broken)
(harness-log " [BROKEN LINK] Node ~a -> ~a" (getf link :source) (getf link :broken-target)))
(dolist (orphan orphans)
(harness-log " [ORPHAN] Node ~a is isolated." orphan)))
(setf *gardener-last-audit* (get-universal-time))
;; Return a log to stop the loop
(list :type :LOG :payload (list :text "Gardener audit complete."))))
(defskill :skill-gardener
:priority 40
:trigger (lambda (ctx)
(let* ((payload (getf ctx :payload))
(sensor (getf payload :sensor)))
(and (eq sensor :heartbeat)
;; Only audit once per day
(> (- (get-universal-time) *gardener-last-audit*) 86400))))
:probabilistic nil
:deterministic #'gardener-deterministic-gate)

View File

@@ -0,0 +1,28 @@
(defun memory-org-to-json (source)
"Converts Org-mode source to JSON AST."
(declare (ignore source))
"")
(defun memory-json-to-org (ast)
"Converts JSON AST back to Org-mode text."
(declare (ignore ast))
"")
(defun memory-normalize-ast (ast)
"Recursively ensures ID uniqueness across the AST."
(declare (ignore ast))
nil)
(defun make-memory-node (headline &key content properties children)
"Constructor for a normalized Org node alist."
(declare (ignore headline))
(list :TYPE :HEADLINE
:PROPERTIES (or properties nil)
:CONTENT content
:CONTENTS children))
(defskill :skill-homoiconic-memory
:priority 100
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))

View File

@@ -0,0 +1,231 @@
(in-package :opencortex)
(defun lisp-validator-check-structural (code-string)
"Checks for balanced parens, brackets, and terminated strings.
Returns (VALUES t nil) if clean, or (VALUES nil reason-string line col)."
(let ((stack nil)
(in-string nil)
(escaped nil)
(line 1)
(col 0)
(last-open-line 1)
(last-open-col 0))
(dotimes (i (length code-string)
(if (null stack)
(values t nil nil nil)
(values nil (format nil "Unbalanced '~a' opened at line ~a, col ~a"
(caar stack) last-open-line last-open-col)
last-open-line last-open-col)))
(let ((ch (char code-string i)))
(cond (escaped (setf escaped nil))
((char= ch #\\) (setf escaped t))
(in-string
(when (char= ch #\") (setf in-string nil)))
((char= ch #\;)
;; Skip to end of line
(loop while (and (< i (1- (length code-string)))
(not (char= (char code-string (1+ i)) #\Newline)))
do (incf i))
(incf line) (setf col 0))
((char= ch #\")
(setf in-string t))
((member ch '(#\( #\[))
(push (list (string ch) line col) stack)
(setf last-open-line line last-open-col col))
((char= ch #\))
(cond ((null stack)
(return-from lisp-validator-check-structural
(values nil (format nil "Unexpected ')' at line ~a, col ~a" line col) line col)))
((string= (caar stack) "[")
(return-from lisp-validator-check-structural
(values nil (format nil "Mismatched ']' expected at line ~a, col ~a" line col) line col)))
(t (pop stack))))
((char= ch #\])
(cond ((null stack)
(return-from lisp-validator-check-structural
(values nil (format nil "Unexpected ']' at line ~a, col ~a" line col) line col)))
((string= (caar stack) "(")
(return-from lisp-validator-check-structural
(values nil (format nil "Mismatched ')' expected at line ~a, col ~a" line col) line col)))
(t (pop stack))))
((char= ch #\Newline)
(incf line) (setf col 0)))
(unless (char= ch #\Newline) (incf col))))))
(defun lisp-validator-check-syntactic (code-string)
"Checks if the code can be read by SBCL with *read-eval* nil.
Returns (VALUES t nil) if clean, or (VALUES nil error-message line col)."
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof) until (eq form :eof)))
(values t nil nil nil))
(error (c)
(let ((msg (format nil "~a" c)))
(values nil msg nil nil)))))
(defparameter *lisp-validator-whitelist*
'(;; Math & Logic
+ - * / = < > <= >= 1+ 1- min max mod abs floor ceiling round
and or not null eq eql equal string= string-equal char= char-equal
;; List Manipulation
list cons car cdr cadr cddr cdar caar caddr cdddr append mapcar remove-if remove-if-not
length reverse sort nth nthcdr push pop last butlast subseq
;; Plists, Alists, and Hash Tables
getf gethash assoc acons pairlis rassoc
;; Control Flow
let let* if cond when unless case typecase prog1 progn
;; Strings
format concatenate string-downcase string-upcase search subseq replace
;; Type predicates
stringp numberp integerp listp symbolp keywordp null
;; Kernel safe symbols
opencortex::harness-log
opencortex::snapshot-memory opencortex::rollback-memory
opencortex::lookup-object opencortex::list-objects-by-type
opencortex::ingest-ast opencortex::find-headline-missing-id
opencortex::context-query-store opencortex::context-get-active-projects
opencortex::context-get-recent-completed-tasks opencortex::context-list-all-skills
opencortex::context-get-system-logs opencortex::context-assemble-global-awareness
opencortex::org-object-id opencortex::org-object-type opencortex::org-object-attributes
opencortex::org-object-content opencortex::org-object-parent-id
opencortex::org-object-children opencortex::org-object-version
opencortex::org-object-last-sync opencortex::org-object-hash
opencortex::org-object-vector
;; Essential macros and special operators
declare ignore quote function lambda defun defvar defparameter defmacro
;; Safe I/O
with-open-file write-string read-line
;; Package introspection
find-package make-package in-package do-external-symbols find-symbol
;; Safe system interaction
uiop:run-program uiop:getenv uiop:merge-pathnames* uiop:file-exists-p
uiop:directory-exists-p uiop:read-file-string uiop:split-string
;; Time
get-universal-time get-internal-real-time sleep
;; Equality
equalp = equal eq eql))
"Static whitelist of symbols permitted in the Lisp Validator sandbox."
(defvar *lisp-validator-registry* nil
"List of dynamically registered safe symbols.")
(defun lisp-validator-register (symbols)
"Adds symbols to the global validator registry."
(setf *lisp-validator-registry*
(append *lisp-validator-registry*
(if (listp symbols) symbols (list symbols))))
(harness-log "LISP VALIDATOR: Registered ~a new safe symbols."
(length (if (listp symbols) symbols (list symbols)))))
(defun lisp-validator-is-safe (symbol)
"Checks if a symbol is in the static whitelist or the dynamic registry."
(or (member symbol *lisp-validator-whitelist* :test #'string-equal)
(member symbol *lisp-validator-registry* :test #'string-equal)))
(defun lisp-validator-ast-walk (form)
"Recursively walks the Lisp AST. Returns T if safe, NIL if unsafe."
(cond
;; Self-evaluating objects are safe.
((or (stringp form) (numberp form) (keywordp form) (characterp form)) t)
;; Symbols used as variables (in non-function position)
((symbolp form) (lisp-validator-is-safe form))
;; Lists represent function calls or special forms.
((listp form)
(let ((head (car form)))
(cond
((eq head 'quote) t)
((not (symbolp head)) nil)
((lisp-validator-is-safe head)
(every #'lisp-validator-ast-walk (cdr form)))
(t
(harness-log "LISP VALIDATOR: Blocked call to non-whitelisted function ~a" head)
nil))))
(t nil)))
(defun lisp-validator-check-semantic (code-string)
"Checks if all symbols in CODE-STRING are whitelisted.
Returns (VALUES t nil) if clean, or (VALUES nil reason-string nil nil)."
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof)
until (eq form :eof)
do (unless (lisp-validator-ast-walk form)
(return-from lisp-validator-check-semantic
(values nil "Code contains non-whitelisted symbols." nil nil)))))
(values t nil nil nil))
(error (c)
(values nil (format nil "Semantic check failed: ~a" c) nil nil))))
(defun lisp-validator-validate (code-string &key strict)
"Validates Lisp code through structural, syntactic, and optional semantic checks.
Returns a plist:
(:status :success :checks (:structural t :syntactic t :semantic t))
or
(:status :error :failed <check-key> :reason <string> :line <n> :col <n>)
When STRICT is non-nil, the semantic whitelist check is enforced.
When STRICT is nil, semantic check is skipped for general validation."
(let ((structural-ok nil) (syntactic-ok nil) (semantic-ok nil)
(reason nil) (line nil) (col nil))
;; Phase 1: Structural
(multiple-value-setq (structural-ok reason line col)
(lisp-validator-check-structural code-string))
(unless structural-ok
(return-from lisp-validator-validate
(list :status :error :failed :structural :reason reason :line line :col col)))
;; Phase 2: Syntactic
(multiple-value-setq (syntactic-ok reason line col)
(lisp-validator-check-syntactic code-string))
(unless syntactic-ok
(return-from lisp-validator-validate
(list :status :error :failed :syntactic :reason reason :line line :col col)))
;; Phase 3: Semantic (only when strict)
(when strict
(multiple-value-setq (semantic-ok reason line col)
(lisp-validator-check-semantic code-string))
(unless semantic-ok
(return-from lisp-validator-validate
(list :status :error :failed :semantic :reason reason :line line :col col))))
;; All clear
(list :status :success
:checks (list :structural t :syntactic t :semantic (or (not strict) semantic-ok)))))
(def-cognitive-tool :validate-lisp
"Deterministically validates Lisp code for structural, syntactic, and semantic correctness.
Use this BEFORE declaring any Lisp code edit complete."
((:code :type :string :description "The Lisp code string to validate.")
(:strict :type :boolean :description "If non-nil, enforces the semantic whitelist."))
:body (lambda (args)
(let ((code (getf args :code))
(strict (getf args :strict)))
(if (and code (stringp code))
(lisp-validator-validate code :strict strict)
(list :status :error :reason "Missing :code argument.")))))
(defskill :skill-lisp-validator
:priority 900
:trigger (lambda (ctx)
;; Trigger on any eval or shell action, or when validation is explicitly requested
(let ((candidate (getf ctx :approved-action)))
(when candidate
(let ((payload (getf candidate :payload)))
(member (getf payload :action) '(:eval :shell))))))
:probabilistic nil
:deterministic (lambda (action context)
(declare (ignore context))
(let ((payload (getf action :payload)))
(if (eq (getf payload :action) :eval)
(let* ((code (getf payload :code))
(result (lisp-validator-validate code :strict t)))
(if (eq (getf result :status) :error)
(progn
(harness-log "LISP VALIDATOR: Blocked unsafe :eval action. ~a"
(getf result :reason))
(list :type :LOG
:payload (list :level :error
:text (format nil "LISP VALIDATOR: Blocked unsafe eval. ~a"
(getf result :reason)))))
action))
action))))

View File

@@ -0,0 +1,33 @@
(in-package :opencortex)
(defun llama-inference (prompt system-prompt &key (model "local-model"))
"Sends a completion request to the local llama.cpp server."
(let ((endpoint (uiop:getenv "LLAMACPP_ENDPOINT")))
(unless endpoint
(harness-log "LLAMA ERROR: LLAMACPP_ENDPOINT not set in environment.")
(return-from llama-inference (list :error "LLAMACPP_ENDPOINT_MISSING")))
(handler-case
(let* ((full-prompt (format nil "System: ~a~%User: ~a~%Assistant:" system-prompt prompt))
(payload (cl-json:encode-json-to-string
`((:prompt . ,full-prompt)
(:n_predict . 1024)
(:stop . ("User:" "System:")))))
(response (dex:post (format nil "~a/completion" endpoint)
:content payload
:headers '(("Content-Type" . "application/json"))))
(data (cl-json:decode-json-from-string response)))
(cdr (assoc :content data)))
(error (c)
(harness-log "LLAMA ERROR: Connection failed -> ~a" c)
(list :error (format nil "~a" c))))))
(progn
(register-probabilistic-backend :llama #'llama-inference)
(harness-log "LLAMA: Local backend registered and active."))
(defskill :skill-llama-backend
:priority 50
:trigger (lambda (ctx) (declare (ignore ctx)) nil) ; Pure infrastructure skill
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))

View File

@@ -0,0 +1,110 @@
(defun get-nested (alist &rest keys)
"Recursively extracts nested values from an alist, handling both objects and arrays."
(let ((val alist))
(dolist (k keys)
;; Descend into arrays (cl-json style: ((key . val)) or ( ( (key . val) ) ))
(loop while (and (listp val) (listp (car val)) (not (keywordp (caar val))))
do (setf val (car val)))
(let ((pair (or (assoc k val)
(assoc (intern (string-upcase (string k)) :keyword) val)
(assoc (intern (string-downcase (string k)) :keyword) val))))
(if pair
(setf val (cdr pair))
(return-from get-nested nil))))
val))
(defun execute-llm-request (prompt system-prompt &key provider model)
"Unified entry point for all LLM providers. Respects the global cascade."
(let* ((active-provider (or provider (car opencortex::*provider-cascade*) :openrouter))
(api-key (vault-get-secret active-provider :type :api-key))
(full-prompt (format nil "~a~%~%Prompt: ~a" system-prompt prompt)))
(harness-log "PROBABILISTIC ENGINE: Requesting ~a (Model: ~s)"
active-provider (or model "default"))
;; If the specifically requested provider has no key, try falling back to the cascade
(when (or (null api-key) (string= api-key ""))
(harness-log "GATEWAY: Provider ~a has no key. Cascade fallback would trigger here." active-provider)
(return-from execute-llm-request (list :status :error :message "API Key missing.")))
(case active-provider
(:gemini-web
(let ((res (uiop:symbol-call :opencortex.skills.org-skill-web-research :ask-gemini-web full-prompt)))
(if res (list :status :success :content res) (list :status :error :message "Web Research Failure"))))
(:ollama
(let* ((host (or (uiop:getenv "OLLAMA_HOST") "localhost:11434"))
(url (format nil "http://~a/api/generate" host))
(body (cl-json:encode-json-to-string `((model . ,(or model "llama3")) (prompt . ,full-prompt) (stream . :false)))))
(handler-case
(progn
(harness-log "LLM DEBUG: Requesting Ollama...")
(let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 60))
(json (cl-json:decode-json-from-string response)))
(list :status :success :content (cdr (assoc :response json)))))
(error (c) (list :status :error :message (format nil "Ollama Failure: ~a" c))))))
(t ;; Cloud Providers (Anthropic, Gemini API, Groq, OpenAI, OpenRouter)
(let* ((endpoint (case active-provider
(:anthropic "https://api.anthropic.com/v1/messages")
(:gemini-api (format nil "https://generativelanguage.googleapis.com/v1/models/~a:generateContent" (or model "gemini-1.5-flash-latest")))
(:groq "https://api.groq.com/openai/v1/chat/completions")
(:openai "https://api.openai.com/v1/chat/completions")
(:openrouter "https://openrouter.ai/api/v1/chat/completions")))
(headers (case active-provider
(:anthropic `(("Content-Type" . "application/json") ("x-api-key" . ,api-key) ("anthropic-version" . "2023-06-01")))
(:gemini-api `(("Content-Type" . "application/json") ("x-goog-api-key" . ,api-key)))
(:openrouter `(("Content-Type" . "application/json") ("Authorization" . ,(format nil "Bearer ~a" api-key))
("HTTP-Referer" . "https://github.com/amr/opencortex") ("X-Title" . "opencortex Autonomous Kernel")))
(t `(("Content-Type" . "application/json") ("Authorization" . ,(format nil "Bearer ~a" api-key))))))
(body (case active-provider
(:anthropic (cl-json:encode-json-to-string `((model . ,(or model "claude-3-5-sonnet-20240620")) (max_tokens . 4096) (system . ,system-prompt) (messages . (( (role . "user") (content . ,prompt) ))))))
(:gemini-api (cl-json:encode-json-to-string `((contents . (((parts . (((text . ,full-prompt))))))))))
(t (cl-json:encode-json-to-string `((model . ,(or model (case active-provider (:groq "llama-3.3-70b-versatile") (t "google/gemini-2.0-flash-001"))))
(messages . (( (role . "system") (content . ,system-prompt) ) ( (role . "user") (content . ,prompt) )))))))))
(handler-case
(progn
(harness-log "LLM DEBUG: Requesting ~a..." active-provider)
(let* ((response (dex:post endpoint :headers headers :content body :connect-timeout 10 :read-timeout 30))
(json (cl-json:decode-json-from-string response)))
(let ((content (case active-provider
(:anthropic (get-nested json :content :text))
(:gemini-api (get-nested json :candidates :parts :text))
(t (get-nested json :choices :message :content)))))
(if content
(list :status :success :content content)
(list :status :error :message (format nil "Failed to parse ~a response structure." active-provider))))))
(error (c) (list :status :error :message (format nil "LLM Gateway Failure (~a): ~a" active-provider c)))))))))
;; Initialize Cascade
(let* ((env-cascade (uiop:getenv "PROVIDER_CASCADE"))
(default-list '(:openrouter :openai :anthropic :groq :gemini-api :ollama))
(final-list (if (and env-cascade (not (string= env-cascade "")))
(mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) :keyword))
(uiop:split-string env-cascade :separator '(#\,)))
default-list)))
(setf opencortex::*provider-cascade* final-list)
(opencortex:harness-log "PROBABILISTIC: Neural Cascade Initialized -> ~a" final-list))
;; Register Providers
(dolist (p '(:anthropic :gemini-api :gemini-web :groq :ollama :openrouter :openai))
(opencortex:register-probabilistic-backend p (lambda (prompt system-prompt &key model)
(execute-llm-request prompt system-prompt :provider p :model model))))
(def-cognitive-tool :ask-llm
"Queries an LLM provider via the unified gateway."
((:prompt :type :string :description "The user prompt.")
(:system-prompt :type :string :description "The system instructions.")
(:provider :type :keyword :description "Optional specific provider.")
(:model :type :string :description "Optional specific model ID."))
:body (lambda (args)
(execute-llm-request (getf args :prompt)
(or (getf args :system-prompt) "You are a helpful assistant.")
:provider (getf args :provider)
:model (getf args :model))))
(defskill :skill-llm-gateway
:priority 150
:trigger (lambda (context) (declare (ignore context)) nil)
:probabilistic (lambda (context) (declare (ignore context)) nil)
:deterministic (lambda (action context) (declare (ignore context)) action))

View File

@@ -0,0 +1,76 @@
(defun context-render-to-org (obj &key depth foveal-id semantic-threshold foveal-vector)
"Recursively renders an org-object with foveal-peripheral pruning.")
(defun context-assemble-global-awareness (&optional signal)
"Assembles the full context block for a neural request.")
(defun context-render-to-org (obj &key (depth 1) (foveal-id nil) (semantic-threshold 0.75) (foveal-vector nil))
"Recursively renders an org-object and its children to an Org string using a Foveal-Peripheral Hybrid model."
(let* ((id (org-object-id obj))
(is-foveal (equal id foveal-id))
(title (or (getf (org-object-attributes obj) :TITLE) "Untitled"))
(content (org-object-content obj))
(children (org-object-children obj))
(stars (make-string depth :initial-element #\*))
(obj-vector (org-object-vector obj))
(similarity (if (and foveal-vector obj-vector (not is-foveal))
(cosine-similarity foveal-vector obj-vector)
0.0))
(is-semantically-relevant (>= similarity semantic-threshold))
;; We always render depth 1 and 2 (Projects and main tasks).
;; We always render the foveal node and its immediate children.
;; We render deeper nodes ONLY if they are semantically relevant.
(should-render (or (<= depth 2) is-foveal is-semantically-relevant))
(output ""))
(when should-render
(setf output (format nil "~a ~a~%:PROPERTIES:~%:ID: ~a~%" stars title id))
(when (and is-semantically-relevant (> similarity 0))
(setf output (concatenate 'string output (format nil ":SEMANTIC_SCORE: ~,2f~%" similarity))))
(setf output (concatenate 'string output (format nil ":END:~%")))
;; Only include full body content if this is the Foveal focus or highly relevant
(when (and content (or is-foveal is-semantically-relevant))
(setf output (concatenate 'string output content (string #\Newline))))
;; Recursively render children
(dolist (child-id children)
(let ((child-obj (lookup-object child-id)))
(when child-obj
;; If the current node is Foveal, its children should be rendered (depth effectively resets)
(let ((next-foveal (if is-foveal child-id foveal-id)))
(setf output (concatenate 'string output
(context-render-to-org child-obj
:depth (1+ depth)
:foveal-id next-foveal
:semantic-threshold semantic-threshold
:foveal-vector foveal-vector))))))))
output))
(defun context-assemble-global-awareness (&optional signal)
"Produces a high-level skeletal outline of the current Memory for the LLM."
(let* ((payload (when signal (getf signal :payload)))
(foveal-id (when payload (getf payload :target-id)))
(foveal-vector (when foveal-id (org-object-vector (lookup-object foveal-id))))
(projects (context-get-active-projects))
(output "GLOBAL MEMEX AWARENESS (Peripheral Vision):
"))
(if projects
(dolist (project projects)
(setf output (concatenate 'string output
(context-render-to-org project
:foveal-id foveal-id
:foveal-vector foveal-vector))))
(setf output (concatenate 'string output "No active projects found.~%")))
output))
(defskill :skill-peripheral-vision
:priority 90
:dependencies ("org-skill-embedding")
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:perceive :context-refresh)))
:probabilistic nil
:deterministic (lambda (action ctx)
(declare (ignore action ctx))
;; This skill primarily provides the context-assemble-global-awareness function
;; used by the probabilistic-gate, rather than handling specific actions.
nil))

View File

@@ -0,0 +1,225 @@
(in-package :opencortex)
(defvar *policy-invariant-priorities*
'((:transparency . 500)
(:autonomy . 400)
(:bloat . 300)
(:modularity . 250)
(:mentorship . 200)
(:sustainability . 100))
"Priority alist for policy invariant conflict resolution.
Higher numbers take precedence.")
(defun policy-check-transparency (action context)
"Ensures the action is inspectable and user-facing actions carry an explanation.
Returns the action if clean, or a blocking LOG event if the action is opaque."
(declare (ignore context))
(unless (listp action)
(return-from policy-check-transparency
(list :type :LOG
:payload (list :level :error
:text "POLICY [Transparency]: Action is not a valid plist. Rejected."))))
(let* ((payload (getf action :payload))
(target (or (getf action :target) (getf action :TARGET)))
(explanation (or (getf payload :explanation) (getf payload :EXPLANATION)
(getf payload :rationale) (getf payload :RATIONALE))))
;; User-facing actions (CLI, TUI, Emacs) must explain themselves
(when (and (member target '(:cli :tui :emacs :EMACS :CLI :TUI))
(not explanation)
(not (member (getf payload :action)
'(:handshake :heartbeat :status-update))))
(return-from policy-check-transparency
(list :type :LOG
:payload (list :level :error
:text "POLICY [Transparency]: User-facing action missing :explanation. Blocked."))))
action))
(defvar *proprietary-domain-watchlist*
'("googleapis.com" "api.openai.com" "anthropic.com" "api.groq.com" "openrouter.ai")
"Domains that represent centralized, proprietary control.
Actions targeting these are logged as autonomy debt, not hard-blocked,
because tactical gateway usage is permitted under the strategic mandate.")
(defun policy-scan-proprietary-references (action)
"Scans ACTION text fields for proprietary domain references.
Returns the first matched domain, or NIL if clean."
(let* ((payload (getf action :payload))
(text (or (getf payload :text) (getf payload :TEXT) ""))
(cmd (or (getf payload :cmd) (getf payload :CMD)
(when (equal (getf payload :tool) "shell")
(getf (getf payload :args) :cmd))
""))
(haystack (concatenate 'string text cmd)))
(dolist (domain *proprietary-domain-watchlist* nil)
(when (search domain haystack)
(return domain)))))
(defun policy-check-autonomy (action context)
"Flags actions that reference proprietary domains. Returns the action
with an autonomy debt log appended, or the action itself if clean."
(declare (ignore context))
(let ((domain (policy-scan-proprietary-references action)))
(if domain
(progn
(harness-log "POLICY [Autonomy]: Detected proprietary reference '~a'. Flagged for replacement." domain)
;; Return a side-effect log but DO NOT block the action
(list :type :LOG
:payload (list :level :warn
:text (format nil "Autonomy Debt: Action references proprietary domain '~a'. Consider a local alternative." domain)
:original-action action)))
action)))
(defvar *policy-max-skill-size-chars* 50000
"Maximum recommended size for a skill file tangled from an Org note.")
(defun policy-check-bloat (action context)
"Warns if a :create-skill action exceeds the bloat threshold.
Does not block, because size alone is not a proof of complexity."
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (getf payload :action))
(content (getf payload :content)))
(when (and (eq act :create-skill)
(stringp content)
(> (length content) *policy-max-skill-size-chars*))
(harness-log "POLICY [Bloat]: Proposed skill is ~a chars. Exceeds ~a char threshold."
(length content) *policy-max-skill-size-chars*)
(return-from policy-check-bloat
(list :type :LOG
:payload (list :level :warn
:text (format nil "Bloat Warning: Proposed skill (~a chars) exceeds ~a char threshold. Review for earned complexity."
(length content) *policy-max-skill-size-chars*)
:original-action action))))
action))
(defvar *mentorship-required-actions*
'(:create-skill :eval :modify-file :write-file :replace :rename-file :delete-file :shell :create-note)
"Actions that trigger the Mentorship invariant.")
(defun policy-check-mentorship (action context)
"Blocks high-impact actions that lack a mentorship note."
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (or (getf payload :action) (getf action :action)))
(note (or (getf payload :mentorship-note) (getf payload :MENTORSHIP-NOTE)))
(target (or (getf action :target) (getf action :TARGET)))
(tool (when (eq target :tool) (getf payload :tool))))
(when (or (member act *mentorship-required-actions*)
(member tool '("shell" "eval" "repair-file")))
(unless note
(return-from policy-check-mentorship
(list :type :LOG
:payload (list :level :error
:text "POLICY [Mentorship]: High-impact action missing :mentorship-note. Explain what you are doing and why. Blocked.")))))
action))
(defvar *cloud-only-backends* '(:openrouter :openai :anthropic :groq :gemini-api)
"Backends that require an internet connection and external infrastructure.")
(defun policy-check-sustainability (action context)
"Logs sustainability debt when the action relies on cloud-only infrastructure.
Does not block, because tactical cloud usage is permitted."
(let* ((payload (getf context :payload))
(backend (getf payload :backend))
(provider (getf payload :provider)))
(when (or (member backend *cloud-only-backends*)
(member provider *cloud-only-backends*))
(harness-log "POLICY [Sustainability]: Cloud provider '~a' used. Logged as sustainability debt."
(or backend provider))
(return-from policy-check-sustainability
(list :type :LOG
:payload (list :level :warn
:text (format nil "Sustainability Debt: Reliance on cloud provider '~a'. Consider Ollama or local inference."
(or backend provider))))))
action))
(defvar *modularity-protected-paths*
'("harness/" "opencortex.asd")
"Paths that constitute the unbreakable core of the system.
Any action targeting these paths must include a :modularity-justification.
This list is project-specific and should be configured at boot time.")
(defun policy-check-modularity (action context)
"Blocks modifications to the system's protected core unless justified."
(declare (ignore context))
(let* ((payload (getf action :payload))
(target-file (or (getf payload :file) (getf payload :filename)))
(justification (or (getf payload :modularity-justification)
(getf payload :MODULARITY-JUSTIFICATION))))
(when (and target-file
(some (lambda (path) (search path target-file)) *modularity-protected-paths*)
(not justification))
(return-from policy-check-modularity
(list :type :LOG
:payload (list :level :error
:text "POLICY [Modularity]: Modification to protected core path blocked. Provide :modularity-justification explaining why this cannot be a skill."
:blocked-path target-file))))
action))
(defun policy-explain (invariant-key message &optional original-action)
"Formats a policy decision into an auditable explanation plist.
INVARIANT-KEY is one of :transparency, :autonomy, :bloat, :modularity, :mentorship, :sustainability.
MESSAGE is a human-readable string.
ORIGINAL-ACTION is the action that was blocked or modified."
(list :type :REQUEST
:target (or (ignore-errors (getf (getf original-action :meta) :source)) :cli)
:payload (list :action :message
:text (format nil "[POLICY ~a] ~a" invariant-key message)
:explanation (format nil "Invariant: ~a | Rationale: ~a" invariant-key message)
:original-action original-action)))
(defun policy-run-invariant-checks (action context)
"Runs all invariant checks in priority order. Returns the final action,
a blocking LOG event, or a warning wrapper."
(let ((checks '(policy-check-transparency
policy-check-autonomy
policy-check-bloat
policy-check-modularity
policy-check-mentorship
policy-check-sustainability)))
(dolist (check-fn checks action)
(let ((result (funcall check-fn action context)))
;; If the check returned a LOG event, treat it as a block/warning
(when (and (listp result)
(member (getf result :type) '(:LOG :EVENT)))
(let ((level (getf (getf result :payload) :level)))
(cond ((eq level :error)
;; Hard block: return the log event directly
(return-from policy-run-invariant-checks result))
(t
;; Warning: log it, but continue with the original action
(harness-log "~a" (getf (getf result :payload) :text))))))))))
(defun policy-find-engineering-standards-gate ()
"Searches for the Engineering Standards gate across known jailed package names.
Returns the function symbol, or NIL if unavailable."
(dolist (pkg-name '(:opencortex.skills.org-skill-engineering-standards
:opencortex.skills.org-skill-engineering
:opencortex.skills.engineering-standards)
nil)
(let ((pkg (find-package pkg-name)))
(when pkg
(let ((sym (find-symbol "ENGINEERING-STANDARDS-GATE" pkg)))
(when (and sym (fboundp sym))
(return (symbol-function sym))))))))
(defun policy-deterministic-gate (action context)
"The main policy gate. Runs invariant checks, then delegates to engineering standards if available.
Never returns NIL silently; always returns an action or an auditable log event."
(let ((current-action (policy-run-invariant-checks action context)))
;; If an invariant returned a blocking log, do not proceed further
(when (and (listp current-action)
(member (getf current-action :type) '(:LOG :EVENT))
(eq (getf (getf current-action :payload) :level) :error))
(return-from policy-deterministic-gate current-action))
;; Delegate to Engineering Standards if loaded
(let ((eng-gate (policy-find-engineering-standards-gate)))
(when eng-gate
(setf current-action (funcall eng-gate current-action context))))
current-action))
(defskill :skill-policy
:priority 500
:trigger (lambda (ctx) (declare (ignore ctx)) t)
:probabilistic nil
:deterministic #'policy-deterministic-gate)

View File

@@ -0,0 +1,44 @@
(in-package :opencortex)
(defun validate-communication-protocol-schema (msg)
"Strict structural validation for incoming communication protocol messages."
(unless (listp msg)
(error "Communication Protocol Schema Error: Message must be a property list (got ~s)" (type-of msg)))
(let ((type (let ((raw (proto-get msg :type))) (if (keywordp raw) (intern (string-upcase (string raw)) :keyword) raw))))
(unless (member type '(:REQUEST :EVENT :RESPONSE :LOG :STATUS :CHAT))
(progn (harness-log "REJECTED MSG: ~s" msg) (error "Communication Protocol Schema Error: Invalid message type '~a'" type)))
(case type
(:REQUEST
;; Allow missing :target if :source is present in :meta, since reason-gate
;; will infer :target from :source downstream. This preserves "equality of
;; clients" — gateways need not duplicate routing logic.
(let ((target (proto-get msg :target))
(source (proto-get (proto-get msg :meta) :source)))
(unless (or target source)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :target and no :source in :meta to infer it"))
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :payload"))))
(:EVENT
(let ((payload (proto-get msg :payload)))
(unless (and payload (listp payload))
(error "Communication Protocol Schema Error: EVENT missing or invalid :payload"))
(unless (or (proto-get payload :action) (proto-get payload :sensor))
(error "Communication Protocol Schema Error: EVENT payload must contain :action or :sensor"))))
(:RESPONSE
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: RESPONSE missing mandatory :payload"))))
t))
(defskill :skill-communication-protocol-validator
:priority 95
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:protocol-received)))
:probabilistic nil
:deterministic (lambda (action ctx)
(declare (ignore ctx))
(validate-communication-protocol-schema action)
action))

View File

@@ -0,0 +1,108 @@
(in-package :opencortex)
(defvar *scribe-last-checkpoint* 0
"The universal-time of the last successful distillation run.")
(defun scribe-load-state ()
"Loads the scribe checkpoint from the state directory."
(let ((state-file (uiop:merge-pathnames* "state/scribe-checkpoint.lisp" (asdf:system-source-directory :opencortex))))
(if (uiop:file-exists-p state-file)
(setf *scribe-last-checkpoint* (read-from-string (uiop:read-file-string state-file)))
(setf *scribe-last-checkpoint* 0))))
(defun scribe-save-state ()
"Saves the current universal-time as the new checkpoint."
(let ((state-file (uiop:merge-pathnames* "state/scribe-checkpoint.lisp" (asdf:system-source-directory :opencortex))))
(ensure-directories-exist state-file)
(with-open-file (out state-file :direction :output :if-exists :supersede)
(format out "~a" (get-universal-time)))))
(defun scribe-get-distillable-nodes ()
"Returns a list of org-objects from the daily/ folder that require distillation."
(let ((results nil))
(maphash (lambda (id obj)
(declare (ignore id))
(let* ((attrs (org-object-attributes obj))
(tags (getf attrs :TAGS))
(type (org-object-type obj))
(version (org-object-version obj)))
(when (and (eq type :HEADLINE)
(> version *scribe-last-checkpoint*)
(not (member "@personal" tags :test #'string-equal)))
(push obj results))))
*memory*)
results))
(defun probabilistic-skill-scribe (context)
"Generates the extraction prompt for the Scribe."
(let* ((payload (getf context :payload))
(nodes (scribe-get-distillable-nodes)))
(if nodes
(let ((text-to-process ""))
(dolist (node nodes)
(setf text-to-process (concatenate 'string text-to-process
(format nil "ID: ~a~%TITLE: ~a~%CONTENT: ~a~%---~%"
(org-object-id node)
(getf (org-object-attributes node) :TITLE)
(org-object-content node)))))
(format nil "DISTILLATION TASK:
Below are raw chronological logs from my daily journal.
Extract ATOMIC EVERGREEN NOTES from this text.
RULES:
1. One note per distinct concept.
2. Output a list of Lisp plists: ((:title \"...\" :content \"...\" :source-id \"...\") ...)
3. The content should be in Org-mode format.
4. Keep titles descriptive and snake_case.
TEXT:
~a" text-to-process))
nil)))
(defun scribe-commit-notes (proposals)
"Writes proposed atomic notes to the notes/ directory. Appends if the note exists."
(let ((notes-dir (uiop:merge-pathnames* "notes/" (asdf:system-source-directory :opencortex))))
(ensure-directories-exist notes-dir)
(dolist (note proposals)
(let* ((title (getf note :title))
(content (getf note :content))
(source-id (getf note :source-id))
(filename (format nil "~a.org" (string-downcase (cl-ppcre:regex-replace-all " " title "_"))))
(path (merge-pathnames filename notes-dir)))
(if (uiop:file-exists-p path)
(with-open-file (out path :direction :output :if-exists :append)
(format out "~%~%* Appended insight from ~a~%~a" source-id content))
(with-open-file (out path :direction :output :if-exists :supersede)
(format out ":PROPERTIES:~%:ID: ~a~%:SOURCE_ID: ~a~%:END:~%#+TITLE: ~a~%~%~a"
(org-id-new) source-id title content)))
(harness-log "SCRIBE: Processed evergreen note ~a" filename)))))
(defun verify-skill-scribe (action context)
"Executes the note creation and marks source nodes as distilled."
(declare (ignore context))
(let ((data (cond ((and (listp action) (eq (getf action :type) :REQUEST))
(getf (getf action :payload) :payload))
((and (listp action) (not (member (getf action :type) '(:LOG :EVENT))))
action)
(t nil))))
(when data
(harness-log "SCRIBE: Committing ~a atomic notes..." (length data))
(scribe-commit-notes data)
(scribe-save-state)
(harness-log "SCRIBE: Distillation complete.")
;; Return a log event to stop the loop
(list :type :LOG :payload (list :text "Distillation successful.")))))
(defskill :skill-scribe
:priority 50
:trigger (lambda (ctx)
(let* ((payload (getf ctx :payload))
(sensor (getf payload :sensor)))
(and (eq sensor :heartbeat)
;; Only run once per hour to check if we need to distill
(> (- (get-universal-time) *scribe-last-checkpoint*) 3600)
(scribe-get-distillable-nodes))))
:probabilistic #'probabilistic-skill-scribe
:deterministic #'verify-skill-scribe)
(scribe-load-state)

View File

@@ -0,0 +1,56 @@
(defparameter *allowed-commands* '("ls" "git" "rg" "grep" "date" "echo" "cat" "node" "python3" "sbcl"))
(defparameter *shell-metacharacters* '(#\; #\& #\| #\> #\< #\$ #\` #\\ #\!))
(defun shell-command-safe-p (cmd-string)
"Returns T if the command string contains no dangerous metacharacters."
(not (some (lambda (char) (find char cmd-string)) *shell-metacharacters*)))
(defun execute-shell-safely (action context)
(let* ((payload (getf action :PAYLOAD))
(cmd-string (getf payload :cmd))
(executable (car (uiop:split-string (string-trim " " cmd-string) :separator '(#\Space)))))
(cond
((not (shell-command-safe-p cmd-string))
(opencortex:inject-stimulus
`(:TYPE :EVENT :PAYLOAD (:SENSOR :shell-response :cmd ,cmd-string :stdout "" :stderr "ERROR - Security Violation: Dangerous metacharacters detected." :exit-code 1))
:stream (getf context :reply-stream)))
((not (member executable *allowed-commands* :test #'string=))
(opencortex:inject-stimulus
`(:TYPE :EVENT :PAYLOAD (:SENSOR :shell-response :cmd ,cmd-string :stdout "" :stderr "ERROR - Command not in security whitelist." :exit-code 1))
:stream (getf context :reply-stream)))
(t
(multiple-value-bind (stdout stderr exit-code)
(uiop:run-program cmd-string :output :string :error-output :string :ignore-error-status t)
(opencortex:inject-stimulus
`(:TYPE :EVENT :PAYLOAD (:SENSOR :shell-response :cmd ,cmd-string :stdout ,(or stdout "") :stderr ,(or stderr "") :exit-code ,exit-code))
:stream (getf context :reply-stream)))))))
(defun trigger-skill-shell-actuator (context)
(let ((type (getf context :TYPE))
(payload (getf context :PAYLOAD)))
(and (eq type :EVENT)
(eq (getf payload :SENSOR) :shell-response))))
(defun probabilistic-skill-shell-actuator (context)
(let* ((p (getf context :PAYLOAD))
(cmd (getf p :cmd))
(stdout (getf p :stdout))
(stderr (getf p :stderr))
(exit-code (getf p :exit-code)))
(format nil "SHELL COMMAND RESULT:
Command: ~a
Exit Code: ~a
STDOUT: ~a
STDERR: ~a" cmd exit-code stdout stderr)))
(opencortex:register-actuator :shell #'execute-shell-safely)
(defskill :skill-shell-actuator
:priority 80
:trigger #'trigger-skill-shell-actuator
:probabilistic #'probabilistic-skill-shell-actuator
:deterministic (lambda (action context) (declare (ignore context)) action))

View File

@@ -8,7 +8,8 @@
"The entry point to the Metabolic Pipeline: Perceive -> Reason -> Act."
(let ((current-signal signal))
(loop while current-signal do
(let ((depth (getf current-signal :depth 0)))
(let ((depth (getf current-signal :depth 0))
(meta (getf current-signal :meta)))
(when (> depth 10) (harness-log "METABOLISM ERROR: Max depth reached.") (return nil))
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
(harness-log "METABOLISM: Interrupted.")
@@ -18,7 +19,14 @@
(progn
(setf current-signal (perceive-gate current-signal))
(setf current-signal (reason-gate current-signal))
(setf current-signal (act-gate current-signal)))
(let ((feedback (act-gate current-signal)))
;; feedback generation
(if feedback
(progn
;; Inherit meta from trigger signal
(unless (getf feedback :meta) (setf (getf feedback :meta) meta))
(setf current-signal feedback))
(setf current-signal nil))))
(error (c)
(let ((sensor (ignore-errors (getf (getf current-signal :payload) :sensor))))
(harness-log "METABOLISM CRASH [~a]: ~a" (or sensor :unknown) c)
@@ -28,29 +36,49 @@
(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)
(setf current-signal (list :type :EVENT :depth (1+ depth) :meta meta
:payload (list :sensor :loop-error :message (format nil "~a" c) :depth depth)))))))))))
(defvar *auto-save-interval* 300
"Save memory to disk every N seconds. Set from MEMORY_AUTO_SAVE_INTERVAL env.")
(defvar *heartbeat-save-counter* 0
"Counter for auto-save triggers.")
(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)))
(let ((interval (or (ignore-errors (parse-integer (uiop:getenv "HEARTBEAT_INTERVAL"))) 60))
(auto-save (or (ignore-errors (parse-integer (uiop:getenv "MEMORY_AUTO_SAVE_INTERVAL"))) *auto-save-interval*)))
(setf *auto-save-interval* auto-save)
(setf *heartbeat-save-counter* 0)
(setf *heartbeat-thread*
(bt:make-thread
(lambda ()
(loop
(sleep interval)
(incf *heartbeat-save-counter*)
(when (>= *heartbeat-save-counter* (/ *auto-save-interval* interval))
(setf *heartbeat-save-counter* 0)
(save-memory-to-disk))
;; inject-stimulus is synchronous for heartbeats, preventing accumulation.
(inject-stimulus (list :type :EVENT :payload (list :sensor :heartbeat :unix-time (get-universal-time))))))
:name "opencortex-heartbeat"))))
(defvar *shutdown-save-enabled* t
"If non-nil, save memory to disk on graceful shutdown.")
(defun main ()
"Entry point for the Skeleton MVP. Handles initialization and graceful shutdown."
(let* ((home (uiop:getenv "HOME"))
(env-file (uiop:merge-pathnames* ".local/share/opencortex/.env" (uiop:ensure-directory-pathname home))))
(when (uiop:file-exists-p env-file) (cl-dotenv:load-env env-file)))
;; Load memory from disk if a snapshot exists
(load-memory-from-disk)
(initialize-actuators)
(initialize-all-skills)
(start-heartbeat)
;; Graceful shutdown handler for SBCL
@@ -58,10 +86,14 @@
(sb-sys:enable-interrupt sb-unix:sigint
(lambda (sig code scp)
(declare (ignore sig code scp))
(harness-log "SHUTDOWN: SIGINT received. Exiting...")
(harness-log "SHUTDOWN: SIGINT received. Saving memory...")
(when *shutdown-save-enabled* (save-memory-to-disk))
(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))
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
(harness-log "SHUTDOWN: Interrupt flag set. Saving memory...")
(when *shutdown-save-enabled* (save-memory-to-disk))
(return))
(sleep sleep-interval))))

View File

@@ -79,6 +79,54 @@
(harness-log "MEMORY - Memory rolled back to snapshot ~a" index))
(harness-log "MEMORY ERROR - Snapshot ~a not found." index))))
(defvar *memory-snapshot-path* nil
"Path to the memory snapshot file. Set from MEMORY_SNAPSHOT_PATH env or default.")
(defun ensure-memory-snapshot-path ()
"Initializes the snapshot path from environment or default location."
(or *memory-snapshot-path*
(let ((env-path (uiop:getenv "MEMORY_SNAPSHOT_PATH")))
(setf *memory-snapshot-path*
(or env-path
(uiop:merge-pathnames* "memory.snap" (user-homedir-pathname)))))))
(defun save-memory-to-disk ()
"Serializes *memory* and *history-store* to disk for crash recovery.
Converts hash tables to alists for proper serialization."
(let ((path (ensure-memory-snapshot-path)))
(with-open-file (stream path :direction :output :if-exists :supersede :if-does-not-exist :create)
(format stream ";; OpenCortex Memory Snapshot~%")
(format stream ";; Created: ~a~%~%" (format nil "~a" (get-universal-time)))
(let ((memory-alist nil)
(history-alist nil))
(maphash (lambda (k v) (push (cons k v) memory-alist)) *memory*)
(maphash (lambda (k v) (push (cons k v) history-alist)) *history-store*)
(prin1 (list :memory memory-alist :history-store history-alist) stream)))
(harness-log "MEMORY - Saved to ~a" path)
path))
(defun load-memory-from-disk ()
"Loads *memory* and *history-store* from disk if the snapshot exists.
Reconstitutes alists into hash tables."
(let ((path (ensure-memory-snapshot-path)))
(when (uiop:file-exists-p path)
(handler-case
(with-open-file (stream path :direction :input)
(let ((data (read stream nil)))
(when data
(let ((memory-alist (getf data :memory))
(history-alist (getf data :history-store)))
(setf *memory* (make-hash-table :test 'equal :size (length memory-alist)))
(dolist (kv memory-alist)
(setf (gethash (car kv) *memory*) (cdr kv)))
(setf *history-store* (make-hash-table :test 'equal :size (length history-alist)))
(dolist (kv history-alist)
(setf (gethash (car kv) *history-store*) (cdr kv)))
(harness-log "MEMORY - Loaded from ~a (~a objects)" path (hash-table-size *memory*))))))
(error (c)
(harness-log "MEMORY WARNING - Failed to load snapshot: ~a" c))))
t))
(defun org-id-new ()
"Generates a new UUID string for Org-mode identification."
(string-downcase (format nil "~a" (uuid:make-v4-uuid))))
@@ -92,6 +140,16 @@
(let ((results nil))
(maphash (lambda (id obj) (declare (ignore id)) (when (eq (org-object-type obj) type) (push obj results))) *memory*)
results))
(defun list-objects-with-attribute (attr-name value)
"Returns a list of all objects where ATTR-NAME matches VALUE."
(let ((results nil))
(maphash (lambda (id obj)
(declare (ignore id))
(let ((attrs (org-object-attributes obj)))
(when (equal (getf attrs attr-name) value)
(push obj results))))
*memory*)
results))
(defun find-headline-missing-id (ast)
"Traverses an AST to find headlines that lack an :ID: property."

View File

@@ -3,6 +3,12 @@
(:export
;; --- communication protocol ---
#:frame-message
#:read-framed-message
#:PROTO-GET
#:LIST-OBJECTS-WITH-ATTRIBUTE
#:COSINE-SIMILARITY
#:VAULT-MASK-STRING
#:*VAULT-MEMORY*
#:parse-message
#:make-hello-message
#:validate-communication-protocol-schema
@@ -34,6 +40,8 @@
#:org-object-hash
#:snapshot-memory
#:rollback-memory
#:save-memory-to-disk
#:load-memory-from-disk
;; --- Context API (Peripheral Vision) ---
#:context-query-store
@@ -112,6 +120,24 @@
(in-package :opencortex)
(defun proto-get (plist key)
"Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions."
(let* ((s (string key))
(up (intern (string-upcase s) :keyword))
(dn (intern (string-downcase s) :keyword)))
(or (getf plist up) (getf plist dn))))
(in-package :opencortex)
(defun proto-get (plist key)
"Robustly retrieves a value from a plist, checking both uppercase and lowercase keyword versions."
(let* ((s (string key))
(up (intern (string-upcase s) :keyword))
(dn (intern (string-downcase s) :keyword)))
(or (getf plist up) (getf plist dn))))
(in-package :opencortex)
(defvar *system-logs* nil)
(defvar *logs-lock* (bt:make-lock "harness-logs-lock"))
(defvar *max-log-history* 100)

View File

@@ -10,8 +10,14 @@
"Enqueues a raw message into the reactive signal pipeline."
(let* ((payload (getf raw-message :payload))
(sensor (getf payload :sensor))
(meta (getf raw-message :meta))
(async-p (or (getf payload :async-p) (member sensor *async-sensors*))))
(when stream (setf (getf raw-message :reply-stream) stream))
;; Ensure META exists and contains the stream if provided
(unless meta (setf meta (list :SOURCE :SYSTEM :SESSION-ID "internal")))
(when stream (setf (getf meta :reply-stream) stream))
(setf (getf raw-message :meta) meta)
(if async-p
(bt:make-thread
(lambda ()
@@ -27,8 +33,9 @@
"Initial processing: Normalizes raw stimuli and updates memory."
(let* ((payload (getf signal :payload))
(type (getf signal :type))
(meta (getf signal :meta))
(sensor (getf payload :sensor)))
(harness-log "GATE [Perceive]: ~a (~a)" type (or sensor "no-sensor"))
(harness-log "GATE [Perceive]: ~a (~a) [Source: ~s]" type (or sensor "no-sensor") (getf meta :source))
(cond ((eq type :EVENT)
(case sensor

BIN
library/reason.fasl Normal file

Binary file not shown.

133
library/reason.lisp Normal file
View File

@@ -0,0 +1,133 @@
(in-package :opencortex)
(defvar *probabilistic-backends* (make-hash-table :test 'equal))
(defvar *provider-cascade* nil)
(defvar *model-selector-fn* nil)
(defvar *consensus-enabled-p* nil)
(defun register-probabilistic-backend (name fn)
"Registers a neural provider (e.g., :gemini, :anthropic) with its calling function."
(setf (gethash name *probabilistic-backends*) fn))
(defun probabilistic-call (prompt &key (system-prompt "You are the Probabilistic engine.") (cascade nil) (context nil))
"Dispatches a neural request through the provider cascade. Returns a Lisp plist or a failure log."
(let ((backends (or cascade *provider-cascade*)))
(or (dolist (backend backends)
(let ((backend-fn (gethash backend *probabilistic-backends*)))
(when backend-fn
(harness-log "PROBABILISTIC: Attempting backend ~a..." backend)
(let* ((model (when *model-selector-fn* (funcall *model-selector-fn* backend context)))
(result (if model
(funcall backend-fn prompt system-prompt :model model)
(funcall backend-fn prompt system-prompt))))
(cond ((and (listp result) (eq (getf result :status) :success))
(return (getf result :content)))
((stringp result) (return result))
(t (harness-log "PROBABILISTIC: Backend ~a failed: ~a" backend (getf result :message))))))))
(list :type :LOG :payload (list :text "Neural Cascade Failure: All providers exhausted.")))))
(defun strip-markdown (text)
"Strips common markdown code block markers from text."
(if (and text (stringp text))
(let ((cleaned text))
(setf cleaned (cl-ppcre:regex-replace-all "^```[a-z]*\\n" cleaned ""))
(setf cleaned (cl-ppcre:regex-replace-all "\\n```$" cleaned ""))
(setf cleaned (cl-ppcre:regex-replace-all "```" cleaned ""))
(string-trim '(#\Space #\Newline #\Tab) cleaned))
text))
(defun normalize-plist-keywords (plist)
"Normalize all keys in a plist to keywords (e.g., TYPE -> :TYPE)."
(when (listp plist)
(loop for (k . rest) on plist by #'cddr
collect (if (and (symbolp k) (not (keywordp k)))
(intern (string k) :keyword)
k)
collect (car rest))))
(defun think (context)
"Generates a Lisp action proposal based on current context."
(let* ((active-skill (find-triggered-skill context))
(tool-belt (generate-tool-belt-prompt))
(global-context (context-assemble-global-awareness))
(system-logs (context-get-system-logs))
(assistant-name (or (uiop:getenv "MEMEX_ASSISTANT") "Agent")))
(let* ((prompt-generator (when active-skill (skill-probabilistic-prompt active-skill)))
(raw-prompt (if prompt-generator
(funcall prompt-generator context)
(let ((p (proto-get (proto-get context :payload) :text)))
(if (and p (stringp p)) p "Maintain metabolic stasis."))))
(system-prompt (format nil "IDENTITY: ~a. MANDATE: Respond with ONE Lisp plist. ~a ~a RECENT_LOGS: ~a
IMPORTANT: To reply to the user, you MUST use:
(:TYPE :REQUEST :PAYLOAD (:ACTION :MESSAGE :TEXT \"<Response Text>\"))
To call a tool, you MUST use:
(:TYPE :REQUEST :TARGET :TOOL :ACTION :CALL :TOOL \"<name>\" :ARGS (:arg1 \"val\"))
MANDATORY VALIDATION RULE: Before declaring any Lisp code edit complete, you MUST call the `:validate-lisp` tool with the proposed code. If the tool returns `:status :error`, read the `:reason` and `:failed` fields, fix the defect, and re-validate. You are strictly forbidden from relying on your own paren-balancing or syntax intuition.
PROVIDER RULE: Always use the default cascade provider unless a specific model or capability is required for the task."
assistant-name global-context tool-belt system-logs)))
(let* ((thought (probabilistic-call raw-prompt :system-prompt system-prompt :context context))
(cleaned (strip-markdown thought))
(meta (proto-get context :meta))
(source (proto-get meta :source)))
(harness-log "THINK: raw cleaned = ~a" (subseq cleaned 0 (min 100 (length cleaned))))
(if (and cleaned (stringp cleaned))
(let ((*read-eval* nil))
(if (and (> (length cleaned) 0) (char= (char cleaned 0) #\())
(handler-case
(let ((parsed (read-from-string cleaned)))
(harness-log "THINK: parsed = ~a" parsed)
(let ((parsed-normalized (normalize-plist-keywords parsed))
(type (proto-get parsed :TYPE))
(target (or (proto-get parsed :TARGET) (proto-get parsed :target))))
(cond ((member type '(:REQUEST :EVENT :STATUS :RESPONSE))
(unless (proto-get parsed :target) (setf (getf parsed :target) (or source :CLI)))
parsed-normalized)
((or (eq target :TOOL) (eq target :tool) (getf parsed :TOOL) (getf parsed :tool)
(and (listp parsed) (listp (car parsed)) (keywordp (caar parsed))))
(list :TYPE :REQUEST :TARGET :TOOL :PAYLOAD (normalize-plist-keywords parsed)))
(t (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned))))))
(error (c) (harness-log "THINK ERROR: ~a" c) (list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned))))
(list :TYPE :REQUEST :TARGET (or source :CLI) :PAYLOAD (list :ACTION :MESSAGE :TEXT cleaned))))
thought)))))
(defun deterministic-verify (proposed-action context)
"Iterates through all skill deterministic-gates sorted by priority."
(let ((current-action proposed-action)
(skills nil))
(maphash (lambda (name skill) (declare (ignore name)) (when (skill-deterministic-fn skill) (push skill skills))) *skills-registry*)
(setf skills (sort skills #'> :key #'skill-priority))
(dolist (skill skills)
(let ((trigger (skill-trigger-fn skill))
(gate (skill-deterministic-fn skill)))
(when (or (null trigger) (ignore-errors (funcall trigger context)))
(let ((next-action (funcall gate current-action context)))
(let ((original-type (proto-get current-action :type)))
(when (and (listp next-action)
(member (proto-get next-action :type) '(:LOG :EVENT :log :event))
(or (not (member original-type '(:LOG :EVENT :log :event)))
(not (eq next-action current-action))))
(harness-log "DETERMINISTIC: Intercepted by skill '~a'" (skill-name skill))
(return-from deterministic-verify next-action)))
(setf current-action next-action)))))
current-action))
(defun reason-gate (signal)
"Unified Stage: Combines Probabilistic proposals and Deterministic verification."
(let* ((type (proto-get signal :type))
(payload (proto-get signal :payload))
(sensor (proto-get payload :sensor)))
(unless (and (eq type :EVENT) (member sensor '(:user-input :chat-message)))
(return-from reason-gate signal))
(let ((candidate (think signal)))
(harness-log "REASON: candidate = ~a" (type-of candidate))
(if (and candidate (listp candidate)
(or (keywordp (car candidate)) (eq (car candidate) 'TYPE) (eq (car candidate) 'type)))
(setf (getf signal :approved-action) (deterministic-verify candidate signal))
(progn
(harness-log "REASON: Invalid candidate type ~a, dropping" (type-of candidate))
(setf (getf signal :approved-action) nil)))
(setf (getf signal :status) :reasoned)
signal)))

View File

@@ -1,5 +1,28 @@
(in-package :opencortex)
(defun COSINE-SIMILARITY (v1 v2)
"Computes the cosine similarity between two vectors.
Both arguments should be sequences of numbers. Returns a value between -1.0 and 1.0."
(let ((len1 (length v1)) (len2 (length v2)))
(if (or (zerop len1) (zerop len2))
0.0
(let ((dot-product 0.0d0)
(norm1 0.0d0)
(norm2 0.0d0))
(let ((len (min len1 len2)))
(dotimes (i len)
(let ((x (coerce (elt v1 i) 'double-float)))
(let ((y (coerce (elt v2 i) 'double-float)))
(incf dot-product (* x y))
(incf norm1 (* x x))
(incf norm2 (* y y))))))
(if (or (zerop norm1) (zerop norm2))
0.0
(/ dot-product (sqrt (* norm1 norm2))))))))
(defun VAULT-MASK-STRING (s) "[MASKED]") ; Stub
(defvar *VAULT-MEMORY* (make-hash-table :test 'equal))
(defstruct skill name priority dependencies trigger-fn probabilistic-prompt deterministic-fn)
(defvar *skill-catalog* (make-hash-table :test 'equal)
@@ -12,11 +35,12 @@
(load-time 0))
(defun find-triggered-skill (context)
"Returns the highest priority skill whose trigger condition matches the context."
"Returns the highest priority skill whose trigger matches context AND has a probabilistic prompt."
(let ((triggered nil))
(maphash (lambda (name skill)
(declare (ignore name))
(when (ignore-errors (funcall (skill-trigger-fn skill) context))
(when (and (skill-probabilistic-prompt skill)
(ignore-errors (funcall (skill-trigger-fn skill) context)))
(push skill triggered)))
*skills-registry*)
(first (sort triggered #'> :key #'skill-priority))))
@@ -103,16 +127,26 @@
(nreverse result))))
(defun validate-lisp-syntax (code-string)
"Checks if a string contains valid, readable Common Lisp forms."
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof) until (eq form :eof))
(values t nil)))
(error (c) (values nil (format nil "~a" c)))))
"Checks if a string contains valid, readable Common Lisp forms.
Delegates to the Lisp Validator skill when available; falls back to a basic
reader check during early boot before the validator skill is loaded."
(let ((result
(if (fboundp 'lisp-validator-validate)
(lisp-validator-validate code-string :strict nil)
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof) until (eq form :eof)))
(list :status :success))
(error (c)
(list :status :error :reason (format nil "~a" c)))))))
(if (eq (getf result :status) :success)
(values t nil)
(values nil (or (getf result :reason) "Lisp Validator rejected code.")))))
(defun load-skill-from-org (filepath)
"Parses and evaluates Lisp blocks from an Org file into a jailed package."
"Parses and evaluates Lisp blocks with :tangle directives from an Org file.
Only loads blocks that specify a .lisp tangle target, ignoring tests and examples."
(let* ((skill-base-name (pathname-name filepath))
(entry (or (gethash skill-base-name *skill-catalog*) (make-skill-entry :filename skill-base-name))))
(setf (skill-entry-status entry) :loading)
@@ -122,39 +156,42 @@
(let* ((content (uiop:read-file-string filepath))
(lines (uiop:split-string content :separator '(#\Newline)))
(in-lisp-block nil)
(collect-this-block nil)
(lisp-code "")
(pkg-name (intern (string-upcase (format nil "OPENCORTEX.SKILLS.~a" skill-base-name)) :keyword)))
(dolist (line lines)
(let ((clean-line (string-trim '(#\Space #\Tab #\Return) line)))
(cond ((uiop:string-prefix-p "#+begin_src lisp" (string-downcase clean-line))
;; Only load blocks that are NOT tangled to src/ or elsewhere
(if (search ":tangle" (string-downcase clean-line))
(setf in-lisp-block nil)
(setf in-lisp-block t)))
(setf in-lisp-block t)
;; Only collect blocks with a :tangle directive pointing to a
;; runtime .lisp file (exclude tests and :tangle no)
(let ((tl (string-downcase clean-line)))
(setf collect-this-block
(and (search ":tangle" tl)
(not (search ":tangle no" tl))
(search ".lisp" tl)
(not (search "tests/" tl))
(not (search "test/" tl))))))
((uiop:string-prefix-p "#+end_src" (string-downcase clean-line))
(setf in-lisp-block nil))
(in-lisp-block
(setf in-lisp-block nil)
(setf collect-this-block nil))
((and in-lisp-block collect-this-block)
(unless (or (uiop:string-prefix-p ":PROPERTIES:" (string-upcase clean-line))
(uiop:string-prefix-p ":END:" (string-upcase clean-line)))
(setf lisp-code (concatenate 'string lisp-code line (string #\Newline))))))))
(if (= (length lisp-code) 0)
(progn (setf (skill-entry-status entry) :ready) t) ;; Valid empty skill
(progn (setf (skill-entry-status entry) :ready) t)
(progn
;; PRE-FLIGHT: Syntax Validation
(multiple-value-bind (valid-p err) (validate-lisp-syntax lisp-code)
(unless valid-p
(error "Syntax Error: ~a" err)))
(unless valid-p (error "Syntax Error: ~a" err)))
(harness-log "HARNESS: Jailing skill '~a' in package ~a" skill-base-name pkg-name)
(unless (find-package pkg-name)
(let ((new-pkg (make-package pkg-name :use '(:cl))))
(do-external-symbols (sym (find-package :opencortex)) (shadowing-import sym new-pkg))))
(use-package :opencortex new-pkg)))
(let ((*read-eval* nil) (*package* (find-package pkg-name)))
(eval (read-from-string (format nil "(progn ~a)" lisp-code))))
(setf (skill-entry-status entry) :ready)
t)))
(error (c)
@@ -197,15 +234,14 @@
(return-from initialize-all-skills nil))
(let ((sorted-files (topological-sort-skills skills-dir)))
;; MANDATE: Configurable mandatory skills must be present for a safe boot
(let* ((mandatory-env (uiop:getenv "MANDATORY_SKILLS"))
(mandatory-skills (if mandatory-env
(mapcar (lambda (s) (string-trim '(#\Space) s))
(uiop:split-string mandatory-env :separator '(#\,)))
(mapcar (lambda (s) (string-trim '(#\Space #\" #\') s))
(uiop:split-string mandatory-env :separator '( #\,)))
'("org-skill-policy" "org-skill-bouncer"))))
(dolist (req mandatory-skills)
(unless (member req sorted-files :key #'pathname-name :test #'string-equal)
(error "BOOT FAILURE: Mandatory skill '~a' not found in skills directory." req)))
(error "BOOT FAILURE: Mandatory skill '~a' not found in skills directory: ~a" req (uiop:native-namestring skills-dir))))
(harness-log "==================================================")
(harness-log " LOADER: Initializing ~a skills..." (length sorted-files))
@@ -220,7 +256,6 @@
(error "BOOT FAILURE: Mandatory skill '~a' failed to load (Status: ~a)." skill-name status)
(harness-log "LOADER WARNING: Skill '~a' failed to load." skill-name))))))
;; Final Summary
(let ((ready 0) (failed 0))
(maphash (lambda (k v)
(declare (ignore k))
@@ -241,7 +276,7 @@ EXAMPLES:
(:target :tool :action :call :tool \"shell\" :args (:cmd \"ls -la\"))
---
")))
" )))
(maphash (lambda (name tool)
(setf output (concatenate 'string output
(format nil "- ~a: ~a~% Parameters: ~s~%~%"

160
library/tui-client.lisp Normal file
View File

@@ -0,0 +1,160 @@
(in-package :cl-user)
(defpackage :opencortex.tui
(:use :cl :croatoan)
(:export :main))
(in-package :opencortex.tui)
(defvar *daemon-host* "127.0.0.1")
(defvar *daemon-port* 9105)
(defvar *socket* nil)
(defvar *stream* nil)
(defvar *chat-history* (list))
(defvar *status-text* "Connecting...")
(defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t))
(defvar *is-running* t)
(defvar *queue-lock* (bt:make-lock))
(defvar *incoming-msgs* nil)
(defun enqueue-msg (msg)
(bt:with-lock-held (*queue-lock*)
(push msg *incoming-msgs*)))
(defun dequeue-msgs ()
(bt:with-lock-held (*queue-lock*)
(let ((msgs (nreverse *incoming-msgs*)))
(setf *incoming-msgs* nil)
msgs)))
(defun clean-keywords (msg)
(if (listp msg)
(let ((clean nil))
(loop for (k v) on msg by #'cddr
do (push (intern (string k) :keyword) clean)
(push v clean))
(nreverse clean))
msg))
(defun format-payload (payload)
"Extracts human-readable text from a protocol payload, handling nested tool calls."
(let* ((action (getf payload :ACTION))
(text (getf payload :TEXT))
(msg (getf payload :MESSAGE))
(tool (getf payload :TOOL))
(prompt (getf payload :PROMPT))
(args (getf payload :ARGS))
(result (getf payload :RESULT)))
(cond (text text)
(msg msg)
((eq action :MESSAGE) (getf payload :TEXT))
((and tool prompt) (format nil "THOUGHT [~a]: ~a" tool prompt))
((and tool args)
(let ((inner-prompt (or (getf args :PROMPT) (getf args :TEXT))))
(if inner-prompt
(format nil "THOUGHT [~a]: ~a" tool inner-prompt)
(format nil "CALL [~a] (ARGS: ~s)" tool args))))
(result (format nil "RESULT: ~a" result))
(t (format nil "~s" payload)))))
(defun listen-thread ()
(loop while *is-running* do
(handler-case
(when (and *stream* (open-stream-p *stream*))
(let ((raw-msg (opencortex:read-framed-message *stream*)))
(unless (member raw-msg '(:eof :error))
(let* ((msg (clean-keywords raw-msg))
(type (or (getf msg :TYPE) (getf msg :type)))
(payload (or (getf msg :PAYLOAD) (getf msg :payload))))
(cond ((and (listp msg) (eq type :EVENT))
(let ((action (or (getf payload :ACTION) (getf payload :action)))
(text (or (getf payload :TEXT) (getf payload :text) (getf payload :MESSAGE) (getf payload :message))))
(cond ((eq action :handshake) (setf *status-text* "Ready"))
(text (enqueue-msg (format nil "SYSTEM: ~a" text))))))
((and (listp msg) (eq type :STATUS))
(setf *status-text* (format nil "[Scribe: ~a] [Gardener: ~a]"
(or (getf msg :SCRIBE) (getf msg :scribe))
(or (getf msg :GARDENER) (getf msg :gardener)))))
((and (listp msg) (member type '(:REQUEST :RESPONSE :LOG)))
(let ((formatted (format-payload payload)))
(when formatted (enqueue-msg formatted))))
((and (listp msg) (eq type :EVENT) (eq (getf payload :SENSOR) :TOOL-OUTPUT))
(let ((formatted (format-payload payload)))
(when formatted (enqueue-msg formatted))))
(t (harness-log "TUI: Ignored unknown type ~a" type)))))
(when (eq raw-msg :eof) (setf *is-running* nil))
(when (eq raw-msg :error) (setf *status-text* "Protocol Error"))))
(error (c) (setf *status-text* (format nil "Net Error: ~a" c)) (setf *is-running* nil)))
(sleep 0.05)))
(defun main ()
(handler-case
(setf *socket* (usocket:socket-connect *daemon-host* *daemon-port*))
(error (e) (format t "Error connecting: ~a~%" e) (return-from main)))
(setf *stream* (usocket:socket-stream *socket*))
(bt:make-thread #'listen-thread :name "tui-listener")
(unwind-protect
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible t)
(let* ((h (height scr))
(w (width scr))
(chat-win (make-instance 'window :height (- h 2) :width w :position (list 0 0)))
(status-win (make-instance 'window :height 1 :width w :position (list (- h 2) 0)))
(input-win (make-instance 'window :height 1 :width w :position (list (- h 1) 0)))
(last-status nil))
(setf (function-keys-enabled-p input-win) t)
(setf (input-blocking input-win) nil)
(loop while *is-running* do
;; 1. Handle incoming messages
(let ((new-msgs (dequeue-msgs)))
(when new-msgs
(dolist (msg new-msgs)
(push msg *chat-history*)
(setf *chat-history* (subseq *chat-history* 0 (min (length *chat-history*) 500))))
(clear chat-win)
(let ((line-num 0))
(dolist (m (reverse (subseq *chat-history* 0 (min (length *chat-history*) (- h 3)))))
(add-string chat-win m :y line-num :x 0)
(incf line-num)))
(refresh chat-win)))
;; 2. Render Status Bar ONLY if changed
(unless (equal *status-text* last-status)
(clear status-win)
(add-string status-win *status-text* :attributes '(:reverse))
(refresh status-win)
(setf last-status *status-text*))
;; 3. Handle Keyboard Input
(let* ((event (get-wide-event input-win))
(ch (and event (typep event 'event) (event-key event))))
(when ch
(cond
((or (eq ch #\Newline) (eq ch #\Return))
(let ((cmd (coerce *input-buffer* 'string)))
(setf (fill-pointer *input-buffer*) 0)
(when (> (length cmd) 0)
;; Local Echo
(enqueue-msg (concatenate 'string "> " cmd))
;; Send to Brain
(let ((framed (opencortex:frame-message (list :TYPE :EVENT
:META (list :SOURCE :tui :SESSION-ID "default")
:PAYLOAD (list :SENSOR :user-input :TEXT cmd)))))
(format *stream* "~a" framed)
(finish-output *stream*)))
(when (string= cmd "/exit") (setf *is-running* nil))))
((or (eq ch :backspace) (eq ch #\Backspace) (eq ch #\Rubout) (eq ch #\Del))
(when (> (length *input-buffer*) 0)
(decf (fill-pointer *input-buffer*))))
((characterp ch)
(vector-push-extend ch *input-buffer*))))
(clear input-win)
(add-string input-win (concatenate 'string "> " (coerce *input-buffer* 'string)))
(move input-win 0 (+ 2 (length *input-buffer*)))
(refresh input-win))
(sleep 0.02))))
(setf *is-running* nil)
(when *socket* (usocket:socket-close *socket*))))

View File

@@ -1,135 +0,0 @@
#+TITLE: Stage 3: Act (act.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:act:
#+STARTUP: content
* Stage 3: Act (act.lisp)
** Architectural Intent: Actuation
The Act stage performs the final side-effects of the reasoning engine. It routes approved actions to their registered physical actuators (CLI, Shell, Emacs, etc.) and handles the execution of internal system tools.
** Actuator Configuration
The core harness can be configured via environment variables to operate silently or target different default outputs.
#+begin_src lisp :tangle ../src/act.lisp
(in-package :opencortex)
(defvar *default-actuator* :cli)
(defvar *silent-actuators* '(:cli :system-message :emacs))
(defun initialize-actuators ()
"Loads actuator routing defaults from environment variables and registers core harness actuators."
(let ((def (uiop:getenv "DEFAULT_ACTUATOR"))
(silent (uiop:getenv "SILENT_ACTUATORS")))
(when def
(setf *default-actuator* (intern (string-upcase def) "KEYWORD")))
(when silent
(setf *silent-actuators*
(mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) "KEYWORD"))
(str:split "," silent)))))
;; Register core harness actuators
(register-actuator :system #'execute-system-action)
(register-actuator :tool #'execute-tool-action))
#+end_src
** Dispatching Actions
The `dispatch-action` function is the primary router. It identifies the target actuator and executes the requested side-effects.
#+begin_src lisp :tangle ../src/act.lisp
(defun dispatch-action (action context)
"Routes an approved action to its registered physical actuator."
(when (and action (listp action))
(let* ((target (or (ignore-errors (getf action :target)) *default-actuator*))
(actuator-fn (gethash target *actuator-registry*)))
(if actuator-fn
(funcall actuator-fn action context)
(harness-log "ACT ERROR: No actuator for ~a" target)))))
#+end_src
** Internal System Actions
The `:system` actuator handles internal harness commands like code evaluation and dynamic skill loading.
#+begin_src lisp :tangle ../src/act.lisp
(defun execute-system-action (action context)
"Processes internal harness commands. (ACTUATOR)"
(declare (ignore context))
(let* ((payload (ignore-errors (getf action :payload)))
(cmd (ignore-errors (getf payload :action))))
(case cmd
(:eval (let ((code (getf payload :code)))
(eval (read-from-string code))))
(:create-skill (let* ((filename (getf payload :filename)) (content (getf payload :content))
(skills-dir (merge-pathnames "skills/" (asdf:system-source-directory :opencortex)))
(full-path (merge-pathnames filename skills-dir)))
(with-open-file (out full-path :direction :output :if-exists :supersede) (write-string content out))
(load-skill-from-org full-path)))
(:message (harness-log "ACT [System]: ~a" (getf payload :text)))
(t (harness-log "ACT ERROR [System]: Unknown command ~s" cmd)))))
#+end_src
** Cognitive Tool Actuation
The `:tool` actuator handles the execution of registered cognitive tools.
#+begin_src lisp :tangle ../src/act.lisp
(defun execute-tool-action (action context)
"Executes a registered cognitive tool. (ACTUATOR)"
(let* ((payload (getf action :payload))
(tool-name (getf payload :tool))
(tool-args (getf payload :args))
(depth (getf context :depth 0))
(tool (gethash (string-downcase (string tool-name)) *cognitive-tools*)))
(if tool
(handler-case
(let* ((clean-args (if (and (listp tool-args) (listp (car tool-args))) (car tool-args) tool-args))
(result (funcall (cognitive-tool-body tool) clean-args)))
(list :type :EVENT :depth (1+ depth) :reply-stream (getf context :reply-stream)
:payload (list :sensor :tool-output :result result :tool tool-name)))
(error (c)
(list :type :EVENT :depth (1+ depth) :reply-stream (getf context :reply-stream)
:payload (list :sensor :tool-error :tool tool-name :message (format nil "~a" c)))))
(list :type :EVENT :depth (1+ depth) :reply-stream (getf context :reply-stream)
:payload (list :sensor :tool-error :message "Tool not found")))))
#+end_src
** The Act Gate
The final stage of the metabolic loop. It performs a "last-mile" safety check before dispatching the action to the registered actuator.
#+begin_src lisp :tangle ../src/act.lisp
(defun act-gate (signal)
"Final Stage: Actuation and feedback generation."
(let* ((approved (getf signal :approved-action))
(type (getf signal :type))
(feedback nil))
;; 1. Last-Mile Safety Check (The Bouncer & Deterministic Gates)
(when approved
(let ((verified (deterministic-verify approved signal)))
(if (and (listp verified) (member (getf verified :type) '(:LOG :EVENT :log :event)))
(progn
(harness-log "ACT BLOCKED: Action failed last-mile deterministic check.")
(setf (getf signal :approved-action) nil)
(setf approved nil)
(setf feedback verified))
(progn
(setf (getf signal :approved-action) verified)
(setf approved verified)))))
;; 2. Actuation Logic
(case type
(:REQUEST (dispatch-action signal signal))
(:EVENT
(when approved
(let* ((target (getf approved :target))
(result (dispatch-action approved signal)))
;; If the actuator returns a signal (like :tool-output), it becomes the feedback.
;; Otherwise, generate tool-output feedback for non-silent actuators.
(cond ((and (listp result) (member (getf result :type) '(:EVENT :LOG)))
(setf feedback result))
((and result (not (member target *silent-actuators*)))
(setf feedback (list :type :EVENT :depth (1+ (getf signal :depth 0))
:reply-stream (getf signal :reply-stream)
:payload (list :sensor :tool-output :result result :tool approved)))))))))
(setf (getf signal :status) :acted)
feedback))
#+end_src

View File

@@ -1,133 +0,0 @@
#+TITLE: Communication Protocol (communication.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:protocol:
#+STARTUP: content
* Communication Protocol (communication.lisp)
** Architectural Intent: Secure Inter-Process Communication & Deterministic Framing
The **communication protocol** defines the exact physical and semantic boundaries for how the isolated Lisp environment talks to the outside world.
The system operates as a perfectly deterministic, highly secure computational engine. When communicating with external processes or actuators—such as an Emacs client, a web dashboard, or a remote Shell script—it cannot rely on unpredictable, "loose" data streams.
Streaming raw Lisp or JSON over a TCP socket is inherently fragile. If a multi-megabyte Org Abstract Syntax Tree (AST) is fragmented by the operating system's network stack during transmission, a standard stream parser might attempt to evaluate an incomplete string, leading to immediate crashes or desynchronization.
To solve this, we implement the **communication protocol**, which enforces absolute deterministic boundaries around every message.
*** 1. Physical Boundary: Hex-Length Prefixing
Every message crossing the wire is prefixed with a strict 6-character hexadecimal length string (zero-padded). This creates an unbreakable physical boundary. The system reads exactly the number of bytes specified by the hex length. It will never under-read (crashing on a partial form) and never over-read (consuming bytes meant for the next message).
*** 2. Actuator-Agnosticism ("Dumb Terminal" Architecture)
The protocol keeps the Lisp environment completely agnostic of its clients. The system does not care if the client is written in Emacs Lisp, Python, or Rust. Any environment capable of calculating a byte length and opening a TCP socket can interface with the Lisp Machine.
*** 3. Preventing Reader Macro Injection
Common Lisp's ~read-from-string~ is extremely powerful but dangerous; it allows "reader macros" (like ~#.~) which execute code during the parsing phase. The communication protocol mandates that ~*read-eval*~ is explicitly bound to ~nil~ before any network data is parsed, physically preventing arbitrary code execution.
** Message Framing Logic
#+begin_src mermaid
flowchart LR
subgraph Client
A[Lisp Property List] --> B[Calculate Length]
B --> C[Hex Prefix: 6 Chars]
C --> D[Concatenate: Length + List]
end
D -- TCP Socket --> System
subgraph System
System --> E[Read 6 Hex Chars]
E --> F[Read Exact Byte Count]
F --> G[Parse S-Expression]
end
#+end_src
** Package Context
We ensure all protocol logic resides within the isolated system namespace.
#+begin_src lisp :tangle ../src/communication.lisp
(in-package :opencortex)
#+end_src
** Actuator Registry
The system maintains a decoupled registry of target actuators. This allows the system to route messages to Emacs, the Shell, or Web Gateways without hardcoding the routing logic into the protocol itself.
#+begin_src lisp :tangle ../src/communication.lisp
(defvar *actuator-registry* (make-hash-table :test 'equal)
"Global registry mapping target keywords to their physical actuator functions.")
(defun register-actuator (name fn)
"Registers an actuator function. Actuators receive: (ACTION CONTEXT)."
(setf (gethash name *actuator-registry*) fn))
#+end_src
** Message Framing (frame-message)
The ~frame-message~ function prepares an outgoing Lisp string for transmission. It calculates the byte length, converts it into a 6-character padded hex string, and prefixes it. If ~COMMUNICATION_PROTOCOL_ENFORCE_HMAC~ is enabled in the environment, it also prepends a cryptographic signature to ensure the message hasn't been tampered with.
#+begin_src lisp :tangle ../src/communication.lisp
(defun frame-message (msg-string)
"Prefixes MSG-STRING with a 6-character hex length.
If security is enabled, prefixes a 64-char HMAC-SHA256 signature."
(let ((len (length msg-string))
(enforce-hmac (uiop:getenv "COMMUNICATION_PROTOCOL_ENFORCE_HMAC")))
(if (and enforce-hmac (string-equal enforce-hmac "true"))
(let ((secret (uiop:getenv "COMMUNICATION_PROTOCOL_HMAC_SECRET")))
(unless secret (error "COMMUNICATION_PROTOCOL_HMAC_SECRET is required when security is enabled."))
(let* ((key (ironclad:ascii-string-to-byte-array secret))
(hmac (ironclad:make-mac :hmac key :sha256))
(payload-bytes (ironclad:ascii-string-to-byte-array msg-string)))
(ironclad:update-mac hmac payload-bytes)
(let ((signature (ironclad:byte-array-to-hex-string (ironclad:produce-mac hmac))))
(format nil "~(~6,'0x~)~a~a" len signature msg-string))))
(format nil "~(~6,'0x~)~a" len msg-string))))
#+end_src
** Message Parsing (parse-message)
Parsing is the high-security inverse of framing. This function acts as the final perimeter defense. It validates the length, verifies the HMAC integrity, and—most importantly—jails the Lisp reader by disabling ~*read-eval*~.
#+begin_src lisp :tangle ../src/communication.lisp
(defun parse-message (framed-string)
"Extracts and parses the S-expression from a framed string securely."
(when (< (length framed-string) 6)
(error "Framed string too short"))
(let* ((enforce-hmac (uiop:getenv "COMMUNICATION_PROTOCOL_ENFORCE_HMAC"))
(use-hmac (and enforce-hmac (string-equal enforce-hmac "true")))
(prefix-len (if use-hmac 70 6)))
(when (< (length framed-string) prefix-len)
(error "Framed string too short for communication protocol prefix"))
(let* ((len-str (subseq framed-string 0 6))
(signature (when use-hmac (subseq framed-string 6 70)))
(actual-msg (subseq framed-string prefix-len))
(expected-len (ignore-errors (parse-integer len-str :radix 16))))
(unless expected-len
(error "Invalid hex length prefix: ~a" len-str))
(unless (= expected-len (length actual-msg))
(error "Message length mismatch. Expected ~a, got ~a" expected-len (length actual-msg)))
(when use-hmac
(let ((secret (uiop:getenv "COMMUNICATION_PROTOCOL_HMAC_SECRET")))
(unless secret (error "COMMUNICATION_PROTOCOL_HMAC_SECRET is required when security is enabled."))
(let* ((key (ironclad:ascii-string-to-byte-array secret))
(hmac (ironclad:make-mac :hmac key :sha256))
(payload-bytes (ironclad:ascii-string-to-byte-array actual-msg)))
(ironclad:update-mac hmac payload-bytes)
(let ((expected-signature (ironclad:byte-array-to-hex-string (ironclad:produce-mac hmac))))
(unless (string-equal signature expected-signature)
(error "communication protocol Integrity Failure: HMAC mismatch"))))))
;; SECURITY: Disable the reader's ability to execute code during parsing
(let ((*read-eval* nil))
(let ((msg (read-from-string actual-msg)))
(validate-communication-protocol-schema msg)
msg)))))
#+end_src
** Handshaking (make-hello-message)
Every session begins with a standard ~HELLO~ handshake, allowing the system to announce its capabilities and protocol version to the connecting client.
#+begin_src lisp :tangle ../src/communication.lisp
(defun make-hello-message (version)
"Constructs the standard HELLO handshake message."
(list :type :EVENT
:payload (list :action :handshake
:version version
:capabilities '(:auth :swank :org-ast))))
#+end_src

View File

@@ -1,99 +0,0 @@
#+TITLE: The Metabolic Loop (loop.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:loop:
#+STARTUP: content
* The Metabolic Loop (loop.lisp)
** Architectural Intent: The Heartbeat
The Metabolic Loop is the high-level coordinator of the OpenCortex. It orchestrates the flow of energy (information) through the system by calling the three metabolic stages in sequence:
1. **Perceive:** Sensory intake.
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 :opencortex)
(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))
(loop while current-signal do
(let ((depth (getf current-signal :depth 0)))
(when (> depth 10) (harness-log "METABOLISM ERROR: Max depth reached.") (return nil))
(when (bt:with-lock-held (*interrupt-lock*) *interrupt-flag*)
(harness-log "METABOLISM: Interrupted.")
(bt:with-lock-held (*interrupt-lock*) (setf *interrupt-flag* nil))
(return nil))
(handler-case
(progn
(setf current-signal (perceive-gate current-signal))
(setf current-signal (reason-gate current-signal))
(setf current-signal (act-gate current-signal)))
(error (c)
(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
** 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.
#+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 "opencortex-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. Handles initialization and graceful shutdown."
(let* ((home (uiop:getenv "HOME"))
(env-file (uiop:merge-pathnames* ".local/share/opencortex/.env" (uiop:ensure-directory-pathname home))))
(when (uiop:file-exists-p env-file) (cl-dotenv:load-env env-file)))
(initialize-actuators)
(initialize-all-skills)
(start-heartbeat)
;; 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

View File

@@ -1,77 +0,0 @@
#+TITLE: Manifest (opencortex.asd)
#+AUTHOR: Amr
#+FILETAGS: :harness:system:
#+STARTUP: content
* Manifest (opencortex.asd)
** Architectural Intent: The ASDF Skeleton
The ~opencortex.asd~ file is the physical blueprint of the Lisp Machine. It uses **Another System Definition Facility (ASDF)** to orchestrate the compilation and loading of all harness modules.
Traditional Lisp systems often use complex, non-linear dependency graphs. However, the ~opencortex~ harness mandates a strict, linear bootstrap sequence.
*** 1. Strict Serial Loading (:serial t)
The harness uses the ~:serial t~ flag. This is a critical design choice that ensures every file is compiled and loaded in the exact order it appears in the ~:components~ list. This eliminates "macro-not-found" errors by guaranteeing that the ~package.lisp~ and ~skills.lisp~ (where the core macros are defined) are always established before any behavioral logic or skills are loaded.
*** 2. Isolation of the Verification Suite
To maintain a "Zero-Overhead" production environment, the testing logic is isolated into a secondary system: ~:opencortex/tests~. This allows the harness to boot in production without loading the ~FiveAM~ framework or the voluminous test data, keeping the memory footprint minimal and the attack surface small.
** The Build Pipeline
#+begin_src mermaid
flowchart TD
Org[Literate Org Files] -- Tangle --> Lisp[Source .lisp Files]
Lisp --> ASDF[ASDF Manifest: .asd]
ASDF --> Loader[SBCL Compiler / Loader]
Loader --> Image[Live Harness Image]
Image -- Build --> Binary[Standalone Binary]
#+end_src
** Harness System Definition
This system defines the core "Thin Harness." It includes the protocol, the object store, and the functional loop.
#+begin_src lisp :tangle ../opencortex.asd
(defsystem :opencortex
:name "opencortex"
:author "Amr"
:version "0.1.0"
:license "AGPLv3"
:description "The Probabilistic-Deterministic Lisp Machine Harness"
:depends-on (:usocket :bordeaux-threads :dexador :uiop :cl-dotenv :cl-ppcre :hunchentoot :ironclad :str :cl-json :uuid)
:serial t
:components ((:file "src/package")
(:file "src/skills")
(:file "src/policy")
(:file "src/communication-validator")
(:file "src/communication")
(:file "src/memory")
(:file "src/context")
(:file "src/probabilistic")
(:file "src/perceive")
(:file "src/reason")
(:file "src/act")
(:file "src/loop"))
:build-operation "program-op"
:build-pathname "opencortex-server"
:entry-point "opencortex:main")
#+end_src
** Verification Suite Definition
This system contains the empirical tests required by the Engineering Standards. It depends on ~:opencortex~ and the ~FiveAM~ testing framework.
#+begin_src lisp :tangle ../opencortex.asd
(defsystem :opencortex/tests
:depends-on (:opencortex :fiveam)
:components ((:file "tests/communication-tests")
(:file "tests/pipeline-tests")
(:file "tests/act-tests")
(:file "tests/boot-sequence-tests")
(:file "tests/memory-tests")
(:file "tests/immune-system-tests"))
:perform (test-op (o s)
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :communication-protocol-suite :opencortex-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :pipeline-suite :opencortex-pipeline-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :safety-suite :opencortex-safety-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :boot-suite :opencortex-boot-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :memory-suite :opencortex-memory-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :immune-suite :opencortex-immune-system-tests))))
#+end_src

View File

@@ -1,82 +0,0 @@
#+TITLE: Stage 1: Perceive (perceive.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:perceive:
#+STARTUP: content
* Stage 1: Perceive (perceive.lisp)
** Architectural Intent: Sensory Ingestion
The Perceive stage is the "sensory cortex" of the OpenCortex. It takes raw stimuli from the outside world (keyboard events, chat messages, heartbeats, or system interrupts) and normalizes them into internal **Signals**.
** Async Sensor Routing
To prevent blocking the main pipeline, certain sensors (like user commands or chat messages) are processed asynchronously in their own threads.
#+begin_src lisp :tangle ../src/perceive.lisp
(in-package :opencortex)
(defvar *async-sensors* '(:chat-message :delegation :user-command)
"List of sensors that should be processed asynchronously to avoid blocking gateways.")
#+end_src
** Foveal Focus State
The system tracks the user's current point of interaction to provide context to the reasoning engine.
#+begin_src lisp :tangle ../src/perceive.lisp
(defvar *foveal-focus-id* nil
"The Org ID of the node the user is currently interacting with.")
#+end_src
** Stimulus Injection
The entry point for raw messages. It determines if the signal should be processed synchronously or asynchronously.
#+begin_src lisp :tangle ../src/perceive.lisp
(defun inject-stimulus (raw-message &key stream (depth 0))
"Enqueues a raw message into the reactive signal pipeline."
(let* ((payload (getf raw-message :payload))
(sensor (getf payload :sensor))
(async-p (or (getf payload :async-p) (member sensor *async-sensors*))))
(when stream (setf (getf raw-message :reply-stream) stream))
(if async-p
(bt:make-thread
(lambda ()
(restart-case (handler-bind ((error (lambda (c) (harness-log "ASYNC ERROR: ~a" c) (invoke-restart 'skip-event))))
(process-signal raw-message))
(skip-event () nil)))
:name "opencortex-async-task")
(restart-case (handler-bind ((error (lambda (c) (harness-log "SYSTEM ERROR: ~a" c) (invoke-restart 'skip-event))))
(process-signal raw-message))
(skip-event () (harness-log "SYSTEM RECOVERY: Stimulus dropped.~%"))))))
#+end_src
** The Perceive Gate
The initial stage of the metabolic loop. It logs the signal, performs selective memory snapshots, and updates the Memory graph based on incoming AST updates.
#+begin_src lisp :tangle ../src/perceive.lisp
(defun perceive-gate (signal)
"Initial processing: Normalizes raw stimuli and updates memory."
(let* ((payload (getf signal :payload))
(type (getf signal :type))
(sensor (getf payload :sensor)))
(harness-log "GATE [Perceive]: ~a (~a)" type (or sensor "no-sensor"))
(cond ((eq type :EVENT)
(case sensor
(:buffer-update
(let ((ast (getf payload :ast)))
(when ast
(snapshot-memory)
(ingest-ast ast))))
(:point-update
(let ((element (getf payload :element)))
(when element
(snapshot-memory)
(setf *foveal-focus-id* (ignore-errors (getf element :id)))
(ingest-ast element))))
(:interrupt
(bt:with-lock-held (*interrupt-lock*) (setf *interrupt-flag* t)))))
((eq type :RESPONSE)
(harness-log "GATE [Perceive]: Act Result -> ~a" (getf payload :status))))
(setf (getf signal :status) :perceived)
(setf (getf signal :foveal-focus) *foveal-focus-id*)
signal))
#+end_src

View File

@@ -1,119 +0,0 @@
#+TITLE: Stage 2: Reason (reason.lisp)
#+AUTHOR: Amr
#+FILETAGS: :harness:reason:
#+STARTUP: content
* Stage 2: Reason (reason.lisp)
** Architectural Intent: Unified Cognition
The Reason stage is the cognitive engine of the OpenCortex. It unifies two distinct reasoning modes:
1. **Probabilistic Reasoning:** Consulting neural models (LLMs) to generate action proposals based on current context.
2. **Deterministic Reasoning:** Running those proposals through a series of deterministic safety gates (Policy, Invariants, and Skill-specific validation) to ensure alignment and security.
** Package and Registry
We initialize the probabilistic backends and the provider cascade which determines the order in which models are consulted.
#+begin_src lisp :tangle ../src/reason.lisp
(in-package :opencortex)
(defvar *probabilistic-backends* (make-hash-table :test 'equal))
(defvar *provider-cascade* nil)
(defvar *model-selector-fn* nil)
(defvar *consensus-enabled-p* nil)
(defun register-probabilistic-backend (name fn)
"Registers a neural provider (e.g., :gemini, :anthropic) with its calling function."
(setf (gethash name *probabilistic-backends*) fn))
#+end_src
** Neural Dispatch (Probabilistic)
The `probabilistic-call` function manages the cascade of neural providers. If the primary provider fails, it automatically falls back to the next one in the list.
#+begin_src lisp :tangle ../src/reason.lisp
(defun probabilistic-call (prompt &key (system-prompt "You are the Probabilistic engine.") (cascade nil) (context nil))
"Dispatches a neural request through the provider cascade. Returns a Lisp plist or a failure log."
(let ((backends (or cascade *provider-cascade*)))
(or (dolist (backend backends)
(let ((backend-fn (gethash backend *probabilistic-backends*)))
(when backend-fn
(harness-log "PROBABILISTIC: Attempting backend ~a..." backend)
(let* ((model (when *model-selector-fn* (funcall *model-selector-fn* backend context)))
(result (if model
(funcall backend-fn prompt system-prompt :model model)
(funcall backend-fn prompt system-prompt))))
;; If the result is valid and doesn't contain a failure log, return it.
(unless (or (null result)
(and (stringp result) (search ":LOG" result)))
(return result))))))
;; Final fallback if all backends in the cascade fail.
(list :type :LOG :payload (list :text "Neural Cascade Failure: All providers exhausted.")))))
#+end_src
** Cognitive Proposal (Think)
The `think` function represents the "intuitive" side of the agent. It identifies the active skill, assembles the global context, and asks the probabilistic engine for a structured action proposal.
#+begin_src lisp :tangle ../src/reason.lisp
(defun think (context)
"Generates a Lisp action proposal based on current context."
(let* ((active-skill (find-triggered-skill context))
(tool-belt (generate-tool-belt-prompt))
(global-context (context-assemble-global-awareness))
(system-logs (context-get-system-logs))
(assistant-name (or (uiop:getenv "MEMEX_ASSISTANT") "Agent")))
(if active-skill
(let* ((prompt-generator (skill-probabilistic-prompt active-skill))
(raw-prompt (when prompt-generator (funcall prompt-generator context)))
(system-prompt (format nil "IDENTITY: Actuator for ~a. MANDATE: ONE Lisp plist. ~a ~a RECENT_LOGS: ~a"
assistant-name global-context tool-belt system-logs)))
(if (and raw-prompt (> (length raw-prompt) 1))
(let* ((thought (probabilistic-call raw-prompt :system-prompt system-prompt :context context))
;; Ensure we are working with a string for read-from-string
(cleaned (if (stringp thought) (string-trim '(#\Space #\Newline #\Tab) thought) thought)))
(if (stringp cleaned)
(handler-case (read-from-string cleaned)
(error (c) (list :type :EVENT :payload (list :sensor :syntax-error :code cleaned :error (format nil "~a" c)))))
cleaned))
(list :type :LOG :payload (list :text (format nil "Skill '~a' triggered (Deterministic only)" (skill-name active-skill))))))
nil)))
#+end_src
** Deterministic Verification
Once a proposal is generated, it MUST pass through the deterministic gates. Every skill can register a gate that inspects and potentially modifies or blocks the proposed action.
#+begin_src lisp :tangle ../src/reason.lisp
(defun deterministic-verify (proposed-action context)
"Iterates through all skill deterministic-gates sorted by priority."
(let ((current-action proposed-action)
(skills nil))
;; 1. Collect and sort active gates
(maphash (lambda (name skill) (declare (ignore name)) (when (skill-deterministic-fn skill) (push skill skills))) *skills-registry*)
(setf skills (sort skills #'> :key #'skill-priority))
;; 2. Execute gates sequentially if their trigger allows
(dolist (skill skills)
(let ((trigger (skill-trigger-fn skill))
(gate (skill-deterministic-fn skill)))
(when (or (null trigger) (ignore-errors (funcall trigger context)))
(setf current-action (funcall gate current-action context))
;; If any gate returns a LOG or EVENT, it has intercepted the action.
(when (and (listp current-action) (member (getf current-action :type) '(:LOG :EVENT :log :event)))
(harness-log "DETERMINISTIC: Intercepted by skill '~a'" (skill-name skill))
(return-from deterministic-verify current-action)))))
current-action))
#+end_src
** The Reason Gate
The entry point for the Reason stage. It orchestrates the transition from probabilistic "thought" to deterministic "verification."
#+begin_src lisp :tangle ../src/reason.lisp
(defun reason-gate (signal)
"Unified Stage: Combines Probabilistic proposals and Deterministic verification."
;; Only process events that haven't been reasoned yet.
(unless (eq (getf signal :type) :EVENT) (return-from reason-gate signal))
(let ((candidate (think signal)))
(if candidate
(setf (getf signal :approved-action) (deterministic-verify candidate signal))
(setf (getf signal :approved-action) nil))
(setf (getf signal :status) :reasoned)
signal))
#+end_src

View File

@@ -1,370 +0,0 @@
#+TITLE: Setup & Onboarding (setup.org)
#+AUTHOR: Amr
#+FILETAGS: :harness:setup:onboarding:
#+STARTUP: content
* Overview: The Zero-to-One Experience
The *Setup & Onboarding* process ensures that users can boot the ~opencortex~ Lisp Machine with zero friction. We follow the *Appliance Paradigm* for standard users (Docker-first) and provide a *Power User Path* (Baremetal) for those wanting deep native integration.
This file is a Literate Devops document. Tangling it generates the Docker configuration and the unified entrypoint script (~opencortex.sh~).
* 1. The Appliance Paradigm (Docker First)
The easiest way to run the agent is via Docker. This prevents the user from having to manually manage SBCL, Quicklisp, Python virtual environments, Playwright binaries, and Java (for Signal).
** The Dockerfile
The container wraps all messy OS dependencies and pre-caches the Lisp environment for rapid booting.
#+begin_src dockerfile :tangle ../Dockerfile
# OPENCORTEX v1.0 Production Environment
FROM debian:bookworm-slim
# Prevent interactive prompts during build
ENV DEBIAN_FRONTEND=noninteractive
# 1. Install System Dependencies
# - sbcl: The Lisp Runtime
# - curl/git/unzip: Standard tools for Quicklisp and binaries
# - default-jre: Required by signal-cli
# - python3/pip: Required for Playwright bridge
# - socat: Required for stateful CLI interaction
RUN apt-get update && apt-get install -y \
sbcl \
curl \
git \
unzip \
default-jre \
libsqlite3-0 \
python3 \
python3-pip \
python3-venv \
emacs-nox \
socat \
&& rm -rf /var/lib/apt/lists/*
# 2. Setup Playwright (High-Fidelity Browsing)
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install playwright \
&& playwright install --with-deps chromium
# 3. Install signal-cli (v0.14.0)
ENV SIGNAL_CLI_VERSION=0.14.0
RUN curl -L https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux.tar.gz | tar xz -C /opt \
&& ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/local/bin/signal-cli
# 4. Install Quicklisp & Pin Distribution
# Pinned to 2026-04-01 for bit-rot resistance.
WORKDIR /root
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp \
&& sbcl --non-interactive \
--load quicklisp.lisp \
--eval '(quicklisp-quickstart:install)' \
--eval '(ql-dist:install-dist "http://beta.quicklisp.org/dist/quicklisp/2026-04-01/distinfo.txt" :prompt nil :replace t)'
# 5. Configure SBCL to load Quicklisp on startup
RUN echo '(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init)))' > /root/.sbclrc
# 6. Setup Application Directory
WORKDIR /app
COPY . /app/projects/opencortex
# 7. Pre-cache Lisp Dependencies
RUN sbcl --non-interactive \
--eval '(push #p"/app/projects/opencortex/" asdf:*central-registry*)' \
--eval '(ql:quickload :opencortex)'
# 8. Environment & Volumes
# The host's memex root should be mounted to /memex
ENV MEMEX_DIR=/memex
VOLUME ["/memex"]
# Default Ports
EXPOSE 9105 8080
# Entrypoint
CMD ["sbcl", "--non-interactive", \
"--eval", "(push #p\"/app/projects/opencortex/\" asdf:*central-registry*)", \
"--eval", "(ql:quickload :opencortex)", \
"--eval", "(opencortex:main)"]
#+end_src
** Docker Compose
#+begin_src yaml :tangle ../docker-compose.yml
services:
opencortex:
build:
context: .
dockerfile: Dockerfile
container_name: opencortex
env_file: .env
volumes:
# Mount the entire memex directory (2 levels up from projects/opencortex)
- ../..:/memex
# Ensure signal-cli state is preserved
- signal-state:/root/.local/share/signal-cli
ports:
- "${ORG_AGENT_DAEMON_PORT:-9105}:9105"
- "${ORG_AGENT_WEB_PORT:-8080}:8080"
restart: unless-stopped
volumes:
signal-state:
#+end_src
* 2. The Unified Entrypoint (opencortex.sh)
We combine the installation script, the daemon launcher, and the CLI chat client into a single, elegant bash script.
If the agent is running, it connects to the chat. If it's installed but offline, it boots the daemon. If it's not installed at all, it walks the user through the onboarding wizard.
#+begin_src bash :tangle ../opencortex.sh :shebang "#!/bin/bash"
set -e
PORT=9105
HOST=${1:-localhost}
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
NC='\033[0m'
command_exists() { command -v "$1" >/dev/null 2>&1; }
update_opencortex() {
echo -e "${BLUE}Updating OpenCortex...${NC}"
if [ -d ".git" ]; then
echo "Pulling latest changes from repository..."
git pull origin main
fi
if [ -f .env ]; then
SKILLS_DIR=$(grep "^SKILLS_DIR=" .env | cut -d"\"" -f2)
SKILLS_DIR=${SKILLS_DIR:-$(pwd)/notes}
echo "Synchronizing core skills to $SKILLS_DIR..."
mkdir -p "$SKILLS_DIR"
cp -n skills/*.org "$SKILLS_DIR/" 2>/dev/null || true
fi
if command_exists docker-compose && [ -f "docker-compose.yml" ]; then
echo "Rebuilding Docker image..."
docker-compose up -d --build
fi
echo -e "${GREEN}✓ Update complete.${NC}"
exit 0
}
if [[ "$1" == "--update" ]]; then
update_opencortex
fi
# 1. Try to drop straight into the CLI chat
if command_exists socat && socat - TCP:$HOST:$PORT,connect-timeout=1 2>/dev/null; then
echo -e "${BLUE}Connected to autonomous brain at $HOST:$PORT...${NC}"
# Use socat with READLINE for history and arrow-key support.
# It establishes a persistent bidirectional connection.
socat READLINE,history=$HOME/.org_agent_history TCP:$HOST:$PORT
exit 0
elif command_exists nc && nc -z $HOST $PORT 2>/dev/null; then
echo -e "${YELLOW}socat not found. Falling back to nc (no line-editing).${NC}"
echo -e "${BLUE}Connected to autonomous brain at $HOST:$PORT...${NC}"
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
echo "$MESSAGE" | nc -N $HOST $PORT
done
exit 0
fi
# 2. Check if we have an existing installation we can boot
if [ -f "$HOME/.opencortex-path" ]; then
INSTALL_DIR=$(cat "$HOME/.opencortex-path")
if [ -d "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/docker-compose.yml" ]; then
echo -e "${YELLOW}Daemon is offline. Booting from $INSTALL_DIR...${NC}"
cd "$INSTALL_DIR"
docker-compose up -d
echo "Waiting for brain to initialize..."
sleep 5
# Re-run to enter chat
exec "$0" "$@"
fi
fi
# 3. If we are running this inside a cloned repo, configure and boot
if [ -f "docker-compose.yml" ] && [ -d "literate" ]; then
echo -e "${YELLOW}Local repository detected. Ensuring configuration...${NC}"
INSTALL_DIR=$(pwd)
echo "$INSTALL_DIR" > "$HOME/.opencortex-path"
if [ ! -f .env ]; then
cp .env.example .env
read -p "What is your name? (default: User): " USER_NAME
USER_NAME=${USER_NAME:-User}
sed -i "s/MEMEX_USER=.*/MEMEX_USER=\"$USER_NAME\"/g" .env
read -p "What shall we name your Assistant? (default: Agent): " AGENT_NAME
AGENT_NAME=${AGENT_NAME:-Agent}
sed -i "s/MEMEX_ASSISTANT=.*/MEMEX_ASSISTANT=\"$AGENT_NAME\"/g" .env
echo -e "\nSelect your primary neural provider:"
echo "1) Google Gemini (Free Tier / Official)"
echo "2) OpenRouter (Unified / Paid)"
echo "3) Anthropic (Claude / API Key)"
echo "4) OpenAI (GPT / API Key)"
read -p "Choice [1-4]: " LLM_CHOICE
case $LLM_CHOICE in
2) read -p "Enter OpenRouter API Key: " INPUT; sed -i "s/OPENROUTER_API_KEY=.*/OPENROUTER_API_KEY=\"$INPUT\"/g" .env ;;
3) read -p "Enter Anthropic API Key: " INPUT; sed -i "s/ANTHROPIC_API_KEY=.*/ANTHROPIC_API_KEY=\"$INPUT\"/g" .env ;;
4) read -p "Enter OpenAI API Key: " INPUT; sed -i "s/OPENAI_API_KEY=.*/OPENAI_API_KEY=\"$INPUT\"/g" .env ;;
*) read -p "Enter Gemini API Key: " INPUT; sed -i "s/GEMINI_API_KEY=.*/GEMINI_API_KEY=\"$INPUT\"/g" .env ;;
esac
# Seed Core Skills
echo -e "\n${BLUE}Seeding Skills...${NC}"
MEMEX_TARGET=$(dirname $(dirname "$INSTALL_DIR"))
SKILLS_DIR=$(grep "^SKILLS_DIR=" .env | cut -d"\"" -f2) ; SKILLS_DIR=${SKILLS_DIR:-$MEMEX_TARGET/notes}
mkdir -p "$SKILLS_DIR"
cp -n skills/*.org "$SKILLS_DIR/" 2>/dev/null || true
echo -e "${GREEN}✓ Core skills seeded to $SKILLS_DIR.${NC}"
fi
docker-compose up -d --build
echo "Waiting for brain to initialize..."
sleep 5
exec "$0" "$@"
fi
# 4. Zero-to-One Onboarding (No installation found)
echo -e "${BLUE}==================================================${NC}"
echo -e "${BLUE} opencortex: Autonomous Intelligence Onboarding ${NC}"
echo -e "${BLUE}==================================================${NC}"
# --- OS & Docker Detection ---
echo -e "\n${BLUE}[1/2] Verifying Environment...${NC}"
install_docker() {
echo -e "${YELLOW}Docker is required to run opencortex natively without messy dependencies.${NC}"
read -p "Would you like me to attempt to install Docker? [Y/n]: " install_choice
install_choice=${install_choice:-Y}
if [[ "$install_choice" =~ ^[Yy]$ ]]; then
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
if command_exists apt-get; then
echo "Installing Docker via apt..."
sudo apt-get update
sudo apt-get install -y docker.io docker-compose
elif command_exists dnf; then
echo "Installing Docker via dnf..."
sudo dnf install -y docker docker-compose
sudo systemctl start docker
sudo systemctl enable docker
else
echo -e "${RED}Unsupported package manager. Please install Docker manually.${NC}"
exit 1
fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
if command_exists brew; then
echo "Installing Docker Desktop via Homebrew..."
brew install --cask docker
echo -e "${YELLOW}Please start Docker Desktop from your Applications folder, then re-run this script.${NC}"
exit 0
else
echo -e "${RED}Homebrew not found. Please install Docker Desktop for Mac manually.${NC}"
exit 1
fi
else
echo -e "${RED}Unsupported OS for automated Docker installation. Please install manually.${NC}"
exit 1
fi
else
echo -e "${RED}Docker is required. Aborting.${NC}"
exit 1
fi
}
if ! command_exists docker || ! command_exists docker-compose; then
install_docker
else
echo -e "${GREEN}✓ Docker and docker-compose detected.${NC}"
fi
# --- Repository Setup ---
echo -e "\n${BLUE}[2/2] Downloading Kernel...${NC}"
MEMEX_DEFAULT="$HOME/memex"
read -p "Where is your Memex located? (default: $MEMEX_DEFAULT): " MEMEX_TARGET
MEMEX_TARGET=${MEMEX_TARGET:-$MEMEX_DEFAULT}
mkdir -p "$MEMEX_TARGET/projects"
cd "$MEMEX_TARGET/projects"
if [ ! -d "opencortex" ]; then
echo "Cloning opencortex..."
git clone https://github.com/gharbeia/opencortex.git
cd opencortex
else
echo -e "${GREEN}✓ Repository already exists.${NC}"
cd opencortex
git pull origin main
fi
mkdir -p "$HOME/.local/bin"
ln -sf "$(pwd)/opencortex.sh" "$HOME/.local/bin/opencortex"
echo -e "${GREEN}✓ Installed 'opencortex' command to ~/.local/bin${NC}"
# Ensure proper ownership if sudo was used for apt
if [ -n "$SUDO_USER" ]; then
chown -R "$SUDO_USER" "$MEMEX_TARGET/projects/opencortex"
fi
# Execute the newly cloned script to run configuration (Step 3)
exec ./opencortex.sh
#+end_src
* 3. The Power-User Path (Baremetal Onboarding)
For users who want to run the Lisp Machine natively on their host OS (typically Emacs users who want the agent to directly manipulate their local =.emacs.d=), we provide the baremetal setup script. This script verifies the host has SBCL and Quicklisp installed, and configures the paths natively.
#+begin_src bash :tangle ../scripts/onboard-baremetal.sh :shebang "#!/bin/bash"
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; NC='\033[0m'
echo -e "${BLUE}=== opencortex: Baremetal Power-User Setup ===${NC}"
if ! command -v sbcl >/dev/null 2>&1; then
echo -e "${RED}✗ SBCL not found. Please install it first.${NC}"
exit 1
fi
if [ ! -d "$HOME/quicklisp" ] && [ ! -d "$HOME/.quicklisp" ]; then
echo -e "${RED}✗ Quicklisp not found. Please install Quicklisp.${NC}"
exit 1
fi
if [ ! -f .env ]; then cp .env.example .env; fi
read -p "What is your name? (default: User): " USER_NAME
USER_NAME=${USER_NAME:-User}
sed -i "s/MEMEX_USER=.*/MEMEX_USER=\"$USER_NAME\"/g" .env
read -p "What shall we name your Assistant? (default: Agent): " AGENT_NAME
AGENT_NAME=${AGENT_NAME:-Agent}
sed -i "s/MEMEX_ASSISTANT=.*/MEMEX_ASSISTANT=\"$AGENT_NAME\"/g" .env
echo "Select primary neural provider:"
echo "1) Gemini"; echo "2) OpenRouter"; echo "3) Anthropic"; echo "4) OpenAI"
read -p "Choice [1-4]: " LLM_CHOICE
case $LLM_CHOICE in
2) read -p "Enter OpenRouter Key: " INPUT; sed -i "s/OPENROUTER_API_KEY=.*/OPENROUTER_API_KEY=\"$INPUT\"/g" .env ;;
3) read -p "Enter Anthropic Key: " INPUT; sed -i "s/ANTHROPIC_API_KEY=.*/ANTHROPIC_API_KEY=\"$INPUT\"/g" .env ;;
4) read -p "Enter OpenAI Key: " INPUT; sed -i "s/OPENAI_API_KEY=.*/OPENAI_API_KEY=\"$INPUT\"/g" .env ;;
*) read -p "Enter Gemini Key: " INPUT; sed -i "s/GEMINI_API_KEY=.*/GEMINI_API_KEY=\"$INPUT\"/g" .env ;;
esac
# Update baremetal paths based on current directory structure
PROJECT_ROOT=$(pwd)
PARENT_DIR=$(dirname "$PROJECT_ROOT")
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$PARENT_DIR\"|g" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$PARENT_DIR/notes\"|g" .env
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$PARENT_DIR/notes\"|g" .env
mkdir -p "$PARENT_DIR/notes"
cp -n skills/*.org "$PARENT_DIR/notes/" 2>/dev/null || true
echo -e "${GREEN}Baremetal setup complete. Run 'make run' to start.${NC}"
#+end_src

View File

@@ -6,18 +6,16 @@
:description "The Probabilistic-Deterministic Lisp Machine Harness"
:depends-on (:usocket :bordeaux-threads :dexador :uiop :cl-dotenv :cl-ppcre :hunchentoot :ironclad :str :cl-json :uuid)
:serial t
:components ((:file "src/package")
(:file "src/skills")
(:file "src/policy")
(:file "src/communication-validator")
(:file "src/communication")
(:file "src/memory")
(:file "src/context")
(:file "src/probabilistic")
(:file "src/perceive")
(:file "src/reason")
(:file "src/act")
(:file "src/loop"))
:components ((:file "library/package")
(:file "library/skills")
(:file "library/communication")
(:file "library/communication-validator")
(:file "library/memory")
(:file "library/context")
(:file "library/perceive")
(:file "library/reason")
(:file "library/act")
(:file "library/loop"))
:build-operation "program-op"
:build-pathname "opencortex-server"
:entry-point "opencortex:main")
@@ -37,3 +35,7 @@
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :boot-suite :opencortex-boot-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :memory-suite :opencortex-memory-tests))
(uiop:symbol-call :fiveam :run! (uiop:find-symbol* :immune-suite :opencortex-immune-system-tests))))
(defsystem :opencortex/tui
:depends-on (:opencortex :croatoan :usocket :bordeaux-threads)
:components ((:file "library/tui-client")))

View File

@@ -2,79 +2,232 @@
set -e
PORT=9105
HOST=${1:-localhost}
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
NC='\033[0m'
HOST="localhost"
RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; YELLOW='\033[0;33m'; NC='\033[0m'
command_exists() { command -v "$1" >/dev/null 2>&1; }
# --- Bootstrap Mode ---
bootstrap_opencortex() {
echo -e "${BLUE}=== OpenCortex: Zero-to-One Bootstrapper ===${NC}"
if [ -d ".git" ]; then
echo "Detected existing repository. Switching to local mode..."
return
fi
# Resolve symlinks to find the actual repository location
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
export SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
TARGET_DIR="opencortex"
if [ -d "$TARGET_DIR" ]; then
if [ -d "$TARGET_DIR/.git" ]; then
echo -e "${YELLOW}! Using existing repository in '$TARGET_DIR'...${NC}"
else
echo -e "${RED}! Directory '$TARGET_DIR' exists. Using it as-is...${NC}"
# Load environment variables if they exist
if [ -f "$SCRIPT_DIR/.env" ]; then
while IFS="=" read -r key value || [ -n "$key" ]; do
if [[ $key =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
val=$(echo "$value" | sed "s/^\"//;s/\"$//")
export "$key=$val"
fi
else
echo -e "${BLUE}Cloning repository into $TARGET_DIR...${NC}"
git clone http://10.10.10.201:3001/amr/opencortex.git "$TARGET_DIR"
fi
cd "$TARGET_DIR"
git submodule update --init --recursive
echo -e "${GREEN}✓ Repository prepared. Handing off to local setup...${NC}"
# Reconnect stdin to the TTY for the next script, or use /dev/null to avoid hang
if [ -t 0 ]; then
exec ./scripts/onboard-baremetal.sh
else
exec ./scripts/onboard-baremetal.sh < /dev/tty 2>/dev/null || exec ./scripts/onboard-baremetal.sh < /dev/null
fi
}
if [ ! -d ".git" ]; then
bootstrap_opencortex
done < "$SCRIPT_DIR/.env"
[ -n "$ORG_AGENT_DAEMON_PORT" ] && PORT=$ORG_AGENT_DAEMON_PORT
[ -n "$DAEMON_HOST" ] && HOST=$DAEMON_HOST
fi
update_opencortex() {
echo -e "${BLUE}Updating OpenCortex...${NC}"
if [ -d ".git" ]; then
git pull origin main
fi
echo -e "${GREEN}✓ Update complete.${NC}"
exit 0
}
if [[ "$1" == "--update" ]]; then update_opencortex; fi
# 1. Try to drop straight into the CLI chat
if command_exists socat && socat - TCP:$HOST:$PORT,connect-timeout=1 2>/dev/null; then
echo -e "${BLUE}Connected to autonomous brain at $HOST:$PORT...${NC}"
socat READLINE,history=$HOME/.org_agent_history TCP:$HOST:$PORT
exit 0
# --- 1. BOOTSTRAP ---
# If the script is run standalone, it clones the full repo and restarts itself.
if [ ! -d "$SCRIPT_DIR/.git" ] && [ ! -d "$HOME/.opencortex" ] && [[ ! "$(pwd)" =~ "opencortex" ]]; then
echo -e "${BLUE}=== OpenCortex: Zero-to-One Bootstrapper ===${NC}"
git clone ssh://git@10.10.10.201:2222/amr/opencortex.git ~/.opencortex
cd ~/.opencortex && git submodule update --init --recursive
exec ./opencortex.sh "$@"
fi
# 2. Local repository detection and launch
if [ -f "opencortex.asd" ] || [ -d "literate" ]; then
# --- 2. SETUP ---
setup_system() {
NON_INTERACTIVE=false
for arg in "$@"; do
if [ "$arg" == "--non-interactive" ]; then NON_INTERACTIVE=true; fi
done
echo -e "${BLUE}=== OpenCortex: Initializing System ===${NC}"
echo -e "${YELLOW}--- Installing System Dependencies ---${NC}"
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y sbcl emacs-nox rlwrap netcat-openbsd curl git socat libssl-dev libncurses5-dev libffi-dev zlib1g-dev libsqlite3-dev
fi
if [ ! -d "$HOME/quicklisp" ]; then
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))"
rm quicklisp.lisp
fi
cd "$SCRIPT_DIR"
if [ ! -f .env ]; then
./scripts/onboard-baremetal.sh
if [ "$NON_INTERACTIVE" = true ]; then
echo "Non-interactive mode: Using environment variables for .env creation."
cp .env.example .env
[ -n "$MEMEX_USER" ] && sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$MEMEX_USER\"|" .env
[ -n "$MEMEX_ASSISTANT" ] && sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$MEMEX_ASSISTANT\"|" .env
[ -n "$OPENROUTER_API_KEY" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$OPENROUTER_API_KEY\"|" .env
[ -n "$MEMEX_DIR" ] && sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$MEMEX_DIR\"|" .env
else
cp .env.example .env
echo -e "\n${YELLOW}--- Identity Configuration ---${NC}"
read -p "Your Name [User]: " user_name < /dev/tty
user_name=${user_name:-User}
sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$user_name\"|" .env
read -p "Agent Name [OpenCortex]: " agent_name < /dev/tty
agent_name=${agent_name:-OpenCortex}
sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$agent_name\"|" .env
echo -e "\n${YELLOW}--- LLM Configuration ---${NC}"
read -p "OpenRouter API Key: " openrouter_key < /dev/tty
[ -n "$openrouter_key" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$openrouter_key\"|" .env
echo -e "\n${YELLOW}--- Memex Folder Structure ---${NC}"
read -p "Memex Root [\$HOME/memex]: " memex_dir < /dev/tty
memex_dir=${memex_dir:-\$HOME/memex}
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$memex_dir\"|" .env
fi
# Hydrate default paths
M_DIR=$(grep MEMEX_DIR .env | cut -d'"' -f2 | sed "s|\$HOME|$HOME|")
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$SCRIPT_DIR/skills\"|" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$M_DIR/notes\"|" .env
mkdir -p "$M_DIR" "$M_DIR/notes" "$M_DIR/areas" "$M_DIR/resources" "$M_DIR/archives" "$M_DIR/system" "$M_DIR/inbox" "$M_DIR/daily" "$M_DIR/projects"
fi
echo -e "${BLUE}Starting OpenCortex via SBCL...${NC}"
sbcl --non-interactive \
--eval "(load \"~/quicklisp/setup.lisp\")" \
--eval "(ql:quickload :opencortex)" \
--eval "(opencortex:main)"
mkdir -p library
for f in harness/*.org skills/*.org; do
emacs -Q --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || true
done
mkdir -p "$HOME/.local/bin"
ln -sf "$SCRIPT_DIR/opencortex.sh" "$HOME/.local/bin/opencortex"
for shell_config in "$HOME/.bashrc" "$HOME/.profile"; do
if [ -f "$shell_config" ]; then
if ! grep -q ".local/bin" "$shell_config"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$shell_config"
fi
fi
done
export PATH="$HOME/.local/bin:$PATH"
echo -e "${YELLOW}--- Compiling and Loading OpenCortex ---${NC}"
sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval "(ql:quickload '(:opencortex :croatoan))"
if [ $? -ne 0 ]; then
echo -e "${RED}✗ Compilation failed.${NC}"
exit 1
fi
if [ "$NON_INTERACTIVE" = true ]; then
echo "Setup complete (Non-interactive)."
exit 0
fi
echo -e "${YELLOW}--- Finalizing: Awakening the Brain ---${NC}"
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
success=false
for i in {1..30}; do
if nc -z localhost $PORT 2>/dev/null; then success=true; break; fi
sleep 2
echo -n "."
done
if [ "$success" = true ]; then
echo -e "\n${GREEN}✓ Brain is alive on port $PORT.${NC}"
exit 0
else
echo -e "\n${RED}✗ Brain failed to wake up.${NC}"
exit 1
fi
}
# --- 3. COMMAND ROUTER ---
COMMAND=$1
[ -z "$COMMAND" ] && COMMAND="cli"
shift || true
DEFAULT_PORT=9105
DEFAULT_HOST="localhost"
TARGET_PORT=${PORT:-$DEFAULT_PORT}
TARGET_HOST=${HOST:-$DEFAULT_HOST}
# If uninitialized, force setup.
if [ ! -f "$SCRIPT_DIR/library/package.lisp" ] || [ ! -f "$SCRIPT_DIR/.env" ]; then
COMMAND="setup"
fi
case "$COMMAND" in
setup)
setup_system "$@"
;;
--boot|boot)
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
if [ -f "$SCRIPT_DIR/.env" ]; then
export OPENROUTER_API_KEY=$(grep OPENROUTER_API_KEY "$SCRIPT_DIR/.env" | cut -d'"' -f2)
fi
exec sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(setf *debugger-hook* (lambda (c h) (declare (ignore h)) (format *error-output* "FATAL LISP ERROR: ~a~%" c) (uiop:print-backtrace :stream *error-output*) (uiop:quit 1)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval '(format t "--- Quickloading OpenCortex ---~%")' --eval "(ql:quickload '(:opencortex :croatoan))" --eval '(opencortex:main)'
;;
tui)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
echo -e "Launching Croatoan TUI..."
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
exec sbcl --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval '(ql:quickload :opencortex/tui)' --eval '(opencortex.tui:main)'
;;
cli)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
if command_exists socat; then
echo -e "Connected to OpenCortex on $TARGET_HOST:$TARGET_PORT (Channel: CLI)"
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
if [ "$MESSAGE" = "/exit" ]; then break; fi
# Frame the message
PAYLOAD="(:TYPE :EVENT :META (:SOURCE :CLI) :PAYLOAD (:SENSOR :USER-INPUT :TEXT \"$MESSAGE\"))"
LEN=$(printf "%s" "$PAYLOAD" | wc -c)
HEXLEN=$(printf "%06x" $LEN)
# Send and read response
(printf "%s%s" "$HEXLEN" "$PAYLOAD" | nc -N $TARGET_HOST $TARGET_PORT) | while read -r LINE; do
CLEAN=$(echo "$LINE" | sed 's/^......//')
if [[ "$CLEAN" == *":TEXT"* ]]; then
TEXT=$(echo "$CLEAN" | sed -n 's/.*:TEXT "\([^"]*\)".*/\1/p')
echo -e "Agent: $TEXT"
fi
done
done
else
echo "Error: socat required for CLI interaction."
exit 1
fi
;;
*)
echo -e "Unknown command: $COMMAND"
echo "Available commands: setup, boot, tui, cli"
exit 1
;;
esac

Binary file not shown.

View File

@@ -1,112 +1,59 @@
#!/bin/bash
# OpenCortex Final-Mile Installer
RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; YELLOW='\033[0;33m'; NC='\033[0m'
echo -e "${BLUE}=== OpenCortex: Baremetal Power-User Setup ===${NC}"
# Robust Non-Blocking Prompt
prompt_user() {
local prompt="$1"
local default="$2"
local var_name="$3"
local result=""
echo -n -e "${YELLOW}$prompt (default: $default): ${NC}" >&2
# Try reading with a short timeout. Use defaults if piped/no-tty.
if read -t 5 result; then
:
else
result="$default"
echo -e "${BLUE} [Auto-Selected: $default]${NC}" >&2
fi
if read -t 5 result; then :; else result="$default"; echo -e "${BLUE} [Auto-Selected: $default]${NC}" >&2; fi
val=${result:-$default}
eval "$var_name=\"$val\""
}
# 1. Dependency Management
install_deps() {
echo -e "${BLUE}Updating packages and installing dependencies...${NC}"
if command -v apt-get >/dev/null; then
sudo apt-get update && sudo apt-get install -y sbcl emacs git curl socat
elif command -v pacman >/dev/null; then
sudo pacman -S --noconfirm sbcl emacs git curl socat
else
echo -e "${RED}✗ Unknown package manager. Please install dependencies manually.${NC}"
fi
}
if ! command -v sbcl >/dev/null 2>&1 || ! command -v emacs >/dev/null 2>&1; then
echo -e "${YELLOW}! Missing dependencies (SBCL/Emacs).${NC}"
prompt_user "Should I attempt to install them for you? [Y/n]" "y" "DO_INSTALL"
if [[ "$DO_INSTALL" =~ ^[Yy]$ ]]; then
install_deps
fi
# 1. Dependencies
if ! command -v sbcl >/dev/null 2>&1; then
echo -e "${BLUE}Installing dependencies...${NC}"
sudo apt-get update && sudo apt-get install -y sbcl emacs git curl socat || true
fi
# 2. Quicklisp Installation
if [ ! -d "$HOME/quicklisp" ] && [ ! -d "$HOME/.quicklisp" ]; then
echo -e "${YELLOW}! Quicklisp not found.${NC}"
prompt_user "Install Quicklisp now? [Y/n]" "y" "DO_QL"
if [[ "$DO_QL" =~ ^[Yy]$ ]]; then
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --non-interactive --load quicklisp.lisp \
--eval "(quicklisp-quickstart:install)" \
--eval "(ql-util:without-prompting (ql:add-to-init-file))"
rm quicklisp.lisp
echo -e "${GREEN}✓ Quicklisp installed.${NC}"
fi
# 2. Quicklisp
if [ ! -d "$HOME/quicklisp" ]; then
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))"
rm quicklisp.lisp
fi
# 3. Literate Tangling (The Transparent Version)
echo -e "${BLUE}Tangling Literate Org files into source code...${NC}"
# 3. Tangling
echo -e "${BLUE}Tangling source files...${NC}"
mkdir -p src
for f in literate/*.org; do
echo -e " - Tangling $f..."
emacs --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || echo -e "${RED} ! Warning: Tangle issue in $f${NC}"
echo " - Tangling $f"
emacs --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1
done
if [ ! -f "src/package.lisp" ]; then
echo -e "${RED}✗ Tangling failed. Essential files missing in src/.${NC}"
exit 1
fi
echo -e "${GREEN}✓ Core tangled successfully.${NC}"
# 4. Environment Configuration
echo -e "\n${BLUE}Configuring your environment...${NC}"
# 4. Config
if [ ! -f .env ]; then cp .env.example .env; fi
prompt_user "What is your name?" "User" "USER_NAME"
sed -i "s/MEMEX_USER=.*/MEMEX_USER=\"$USER_NAME\"/g" .env
prompt_user "What shall we name your Assistant?" "OpenCortex" "AGENT_NAME"
prompt_user "Select provider (1:Gemini, 2:OpenRouter)" "1" "LLM_CHOICE"
sed -i "s/MEMEX_USER=.*/MEMEX_USER=\"$USER_NAME\"/g" .env
sed -i "s/MEMEX_ASSISTANT=.*/MEMEX_ASSISTANT=\"$AGENT_NAME\"/g" .env
echo -e "\nSelect neural provider (1:Gemini, 2:OpenRouter, 3:Anthropic, 4:OpenAI)"
prompt_user "Choice" "1" "LLM_CHOICE"
case $LLM_CHOICE in
2) prompt_user "Enter OpenRouter Key" "" "INPUT"; sed -i "s/OPENROUTER_API_KEY=.*/OPENROUTER_API_KEY=\"$INPUT\"/g" .env ;;
3) prompt_user "Enter Anthropic Key" "" "INPUT"; sed -i "s/ANTHROPIC_API_KEY=.*/ANTHROPIC_API_KEY=\"$INPUT\"/g" .env ;;
4) prompt_user "Enter OpenAI Key" "" "INPUT"; sed -i "s/OPENAI_API_KEY=.*/OPENAI_API_KEY=\"$INPUT\"/g" .env ;;
*) prompt_user "Enter Gemini Key" "" "INPUT"; sed -i "s/GEMINI_API_KEY=.*/GEMINI_API_KEY=\"$INPUT\"/g" .env ;;
esac
# 5. Path Alignment
PROJECT_ROOT=$(pwd)
PARENT_DIR=$(dirname "$PROJECT_ROOT")
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$PARENT_DIR\"|g" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$PARENT_DIR/notes\"|g" .env
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$PROJECT_ROOT/skills\"|g" .env
INSTALL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$(dirname "$INSTALL_DIR")\"|g" .env
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$INSTALL_DIR/skills\"|g" .env
mkdir -p "$PARENT_DIR/notes"
echo -e "${GREEN}✓ Configuration complete.${NC}"
mkdir -p "$HOME/.local/bin"
ln -sf "$INSTALL_DIR/opencortex.sh" "$HOME/.local/bin/opencortex"
echo -e "${GREEN}✓ Installed 'opencortex' command to ~/.local/bin${NC}"
# 6. Final Instructions
echo -e "\n${GREEN}==============================================${NC}"
echo -e "${GREEN} OpenCortex Installation Complete! ${NC}"
echo -e "${GREEN}==============================================${NC}"
echo -e "Your brain is ready to boot."
echo -e "\nTo start the session: ${YELLOW}./opencortex.sh${NC}"
echo -e "To add API keys later, edit: ${YELLOW}$(pwd)/.env${NC}\n"
echo -e " OpenCortex Installation Complete! "
echo -e "==============================================${NC}"
echo -e "To start: opencortex"

View File

@@ -7,14 +7,33 @@ HOST=${1:-localhost}
if command -v socat >/dev/null 2>&1; then
# Use socat with READLINE for history and arrow-key support.
# It establishes a persistent bidirectional connection.
socat READLINE,history=$HOME/.org_agent_history TCP:$HOST:$PORT
else
# Fallback to nc (netcat) for a single-shot connection if socat is missing.
# Note: This is less robust for agents with long-thinking times.
echo "WARNING: socat not found. Falling back to nc (no line-editing support)."
# Note: socat READLINE doesn't handle hex-length framing automatically for input.
# We use a wrapper to frame the message.
echo "Connected to OpenCortex on $HOST:$PORT (Channel: CLI)"
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
echo "$MESSAGE" | nc -N $HOST $PORT
if [ "$MESSAGE" = "/exit" ]; then break; fi
# Frame the message: (:TYPE :EVENT :META (:SOURCE :CLI) :PAYLOAD (:SENSOR :USER-INPUT :TEXT "msg"))
PAYLOAD="(:TYPE :EVENT :META (:SOURCE :CLI) :PAYLOAD (:SENSOR :USER-INPUT :TEXT \"$MESSAGE\"))"
LEN=$(printf "%s" "$PAYLOAD" | wc -c)
HEXLEN=$(printf "%06x" $LEN)
# Send and read response
(printf "%s%s" "$HEXLEN" "$PAYLOAD" | nc -N $HOST $PORT) | while read -r LINE; do
# The line will have the 6-char hex length prefix.
# We strip it and look for the response.
CLEAN=$(echo "$LINE" | sed 's/^......//')
if [[ "$CLEAN" == *":TEXT"* ]]; then
# Extract the text content (simple grep-like extraction for CLI fallback)
TEXT=$(echo "$CLEAN" | sed -n 's/.*:TEXT "\([^"]*\)".*/\1/p')
echo "Agent: $TEXT"
fi
done
done
else
echo "Error: socat or nc required."
exit 1
fi

85
scripts/ui_driver.py Executable file
View File

@@ -0,0 +1,85 @@
import pty
import os
import sys
import time
import select
import re
class VirtualTerminal:
def __init__(self, rows=24, cols=80):
self.rows = rows
self.cols = cols
self.buffer = [[' ' for _ in range(cols)] for _ in range(rows)]
self.cursor_y = 0
self.cursor_x = 0
def _strip_ansi(self, text):
# Very basic ANSI parser for cursor moves and clears
# CSI n ; m H (cursor move)
# CSI J (clear screen)
# CSI K (clear line)
# This is a simplified state machine
parts = re.split(r'(\x1b\[[0-9;?]*[a-zA-Z])', text)
for part in parts:
if part.startswith('\x1b['):
cmd = part[-1]
params = part[2:-1].split(';')
if cmd == 'H' or cmd == 'f': # Move cursor
self.cursor_y = int(params[0]) - 1 if params[0] else 0
self.cursor_x = int(params[1]) - 1 if (len(params) > 1 and params[1]) else 0
elif cmd == 'J': # Clear
mode = int(params[0]) if params[0] else 0
if mode == 2: # Full clear
self.buffer = [[' ' for _ in range(self.cols)] for _ in range(self.rows)]
elif cmd == 'm': # Attributes - ignore for now
pass
else:
for char in part:
if char == '\n':
self.cursor_y += 1
self.cursor_x = 0
elif char == '\r':
self.cursor_x = 0
elif 0 <= self.cursor_y < self.rows and 0 <= self.cursor_x < self.cols:
self.buffer[self.cursor_y][self.cursor_x] = char
self.cursor_x += 1
def get_screen(self):
return "\n".join(["".join(row) for row in self.buffer])
def run_test(command, input_sequence, wait_time=5):
pid, fd = pty.fork()
if pid == 0:
os.environ["TERM"] = "xterm"
os.environ["COLUMNS"] = "80"
os.environ["LINES"] = "24"
os.execvp(command[0], command)
else:
vt = VirtualTerminal()
start_time = time.time()
input_sent = False
while time.time() - start_time < wait_time:
r, w, e = select.select([fd], [], [], 0.1)
if fd in r:
try:
data = os.read(fd, 8192).decode(errors='ignore')
vt._strip_ansi(data)
except OSError:
break
if not input_sent and time.time() - start_time > 2:
os.write(fd, input_sequence.encode())
input_sent = True
os.kill(pid, 9)
os.waitpid(pid, 0)
return vt
if __name__ == "__main__":
# Example usage: python3 ui_driver.py sbcl --eval ...
vt = run_test(sys.argv[1:], "Hi\r", wait_time=10)
print("--- VIRTUAL SCREEN SNAPSHOT ---")
print(vt.get_screen())
print(f"--- CURSOR POSITION: ({vt.cursor_y}, {vt.cursor_x}) ---")

View File

@@ -1,156 +1,381 @@
:PROPERTIES:
:ID: bouncer-agent-skill
:CREATED: [2026-04-11 Sat 15:20]
:EDITED: [2026-04-13 Mon 18:35]
:EDITED: [2026-04-22 Wed 16:00]
:END:
#+DEPENDS_ON: org-skill-credentials-vault
#+TITLE: SKILL: Deterministic Engine Bouncer (Authorization Gate)
#+STARTUP: content
#+FILETAGS: :system:bouncer:authorization:autonomy:
* Overview
The *Deterministic Engine Bouncer* is the authorization gate for high-risk actions. It serializes intercepted actions into Org nodes ("Flight Plans") and re-injects them once manually approved by the Autonomous.
The *Bouncer Skill* is the physical security layer of OpenCortex. While the Policy skill enforces constitutional invariants (transparency, autonomy, modularity), the Bouncer enforces operational security checks.
Think of Policy as the constitution and Bouncer as the bouncer at the door:
- **Policy** asks: "Is this action aligned with our values?"
- **Bouncer** asks: "Is this action safe to execute?"
** The Flight Plan Pattern
High-risk actions don't simply pass or fail—they can enter the "Flight Plan" approval workflow:
1. Bouncer intercepts a risky action
2. Creates an Org node ("Flight Plan") describing the action
3. User manually approves the flight plan in Emacs
4. Bouncer detects approval on next heartbeat
5. Action is re-injected with `approved = t` flag, bypassing the gate
This creates human-in-the-loop oversight for dangerous operations without blocking the system entirely.
** Why a Separate Skill?**
Security and policy are separated for clarity and auditability:
- Policy decisions can be explained (they reference invariants)
- Bouncer decisions are technical (they reference threat vectors)
When something is blocked, the logs clearly show which layer blocked it and why.
* Package Context
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(in-package :opencortex)
#+end_src
* Deep Packet Inspection (DPI)
The Bouncer ensures the action is "safe" by inspecting the payload content via Deep Packet Inspection.
* Security Vectors
** Secret Exposure Check
Retrieves all active secrets from the vault and scans the payload for potential leaks.
The Bouncer implements the 5-Vector security model:
#+begin_src lisp
| Vector | Threat | Response |
|--------|--------|----------|
| Secret Exposure | API keys, passwords in output | Hard block |
| Network Exfiltration | Data sent to unauthorized hosts | Approval required |
| Shell Execution | Arbitrary command execution | Approval required |
| File Modification | Writing/deleting files | Soft check |
| Eval Execution | Arbitrary code evaluation | Approval required |
** Secret Exposure Detection
The vault stores sensitive credentials. This check scans action text for vault secrets to prevent accidental exposure.
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defun bouncer-scan-secrets (text)
"Returns the name of the secret found in TEXT, or NIL if clean."
"Scans TEXT for known secrets from the vault.
RETURNS: The name of the matched secret, or NIL if text is clean.
This prevents the catastrophic failure mode where the agent
accidentally echoes an API key in its response or log output.
The check uses substring matching (not regex) for reliability.
Only secrets longer than 5 characters are checked to avoid
false positives on common words."
(when (and text (stringp text))
(let ((found-secret nil))
(maphash (lambda (key val)
;; Only check secrets of meaningful length
(when (and val (stringp val) (> (length val) 5))
;; Search for secret value in action text
(when (search val text)
(setf found-secret key))))
*vault-memory*)
opencortex::*vault-memory*)
found-secret)))
#+end_src
** Network Exfiltration Check
Inspects shell commands for unwhitelisted domains or IP addresses.
** Network Exfiltration Detection
Detects when shell commands try to send data to untrusted network destinations.
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defvar *bouncer-network-whitelist*
'("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")
"Domains that the Bouncer considers safe for outbound connections.
This whitelist should be minimal—only services explicitly configured
as gateways. All other outbound connections require approval.")
#+begin_src lisp
(defun bouncer-check-network-exfil (cmd)
"Returns T if the command appears to target an unwhitelisted external host."
"Detects if CMD attempts to contact an unwhitelisted external host.
Returns T if the command targets an unknown external host.
Returns NIL if the command is clean or only contacts whitelisted hosts.
The check looks for HTTP/HTTPS/FTP URLs and extracts the domain.
If the domain isn't in *bouncer-network-whitelist*, it's flagged."
(when (and cmd (stringp cmd))
;; Basic check for common data exfiltration tools being used with IPs/URLs
(let ((network-whitelist '("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")))
(when (cl-ppcre:scan "(http|https|ftp)://([\\w\\.-]+)" cmd)
(multiple-value-bind (match regs)
(cl-ppcre:scan-to-strings "(http|https|ftp)://([\\w\\.-]+)" cmd)
(declare (ignore match))
(let ((domain (aref regs 1)))
(not (some (lambda (safe) (search safe domain)) network-whitelist))))))))
;; Look for URL patterns in the command
(when (cl-ppcre:scan "(http|https|ftp)://([\\w\\.-]+)" cmd)
(multiple-value-bind (match regs)
(cl-ppcre:scan-to-strings "(http|https|ftp)://([\\w\\.-]+)" cmd)
(declare (ignore match))
(let ((domain (aref regs 1)))
;; Check if domain is whitelisted
(not (some (lambda (safe) (search safe domain))
*bouncer-network-whitelist*)))))))
#+end_src
* Runtime Guard (bouncer-check)
The primary entry point for all high-impact actions. It blocks or queues actions based on risk vectors.
* Runtime Guard
#+begin_src lisp
** bouncer-check: Main Security Gate
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defun bouncer-check (action context)
"The 5-Vector security gate. Blocks or queues actions based on risk."
"The 5-Vector security gate for high-risk actions.
Evaluates an action against all security vectors and either:
- Returns the action unchanged (pass)
- Returns a blocking LOG event (hard block)
- Returns an approval-required EVENT (soft block)
Vector evaluation order:
1. Already approved actions pass immediately
2. Secret exposure → hard block
3. Network exfiltration → approval required
4. High-impact targets → approval required
The context parameter is not used directly but provided for
consistency with the skill gate signature."
(declare (ignore context))
(let* ((target (getf action :target))
(payload (getf action :payload))
(text (or (getf payload :text) (getf action :text)))
;; Extract cmd from direct shell or tool-mediated shell call
(cmd (or (getf payload :cmd)
(when (and (eq target :tool) (equal (getf payload :tool) "shell"))
(getf (getf payload :args) :cmd))))
(when (and (eq target :tool)
(equal (getf payload :tool) "shell"))
(getf (getf payload :args) :cmd))))
(approved (getf action :approved)))
(cond
;; 0. Bypass for already approved actions
(approved action)
;; 1. Secret Exposure Vector (Hard Block)
(cond
;; Vector 0: Already approved actions pass through
(approved
action)
;; Vector 1: Secret Exposure (Hard Block)
;; If any vault secret is found in the action text, block immediately
((and text (bouncer-scan-secrets text))
(let ((secret-name (bouncer-scan-secrets text)))
(harness-log "SECURITY VIOLATION: Blocked leak of secret ~a" secret-name)
`(:type :log :payload (:level :error :text ,(format nil "Action blocked: Potential exposure of ~a" secret-name)))))
(harness-log "SECURITY VIOLATION: Blocked potential leak of secret '~a'" secret-name)
(list :type :LOG
:payload (list :level :error
:text (format nil "Action blocked: Potential exposure of '~a'" secret-name)))))
;; 2. Network Exfiltration Vector (Authorization Required)
((and (or (eq target :shell)
(and (eq target :tool) (equal (getf payload :tool) "shell")))
;; Vector 2: Network Exfiltration (Soft Block)
;; Shell commands targeting unknown hosts require approval
((and (or (eq target :shell)
(and (eq target :tool)
(equal (getf payload :tool) "shell")))
(bouncer-check-network-exfil cmd))
(harness-log "SECURITY WARNING: External network call detected. Queuing for approval.")
`(:type :EVENT :payload (:sensor :approval-required :action ,action)))
;; 3. High-Impact Target Vector (Authorization Required)
(list :type :EVENT
:payload (list :sensor :approval-required
:action action)))
;; Vector 3: High-Impact Targets (Soft Block)
;; Shell execution, file repair, and eval require approval
((or (member target '(:shell))
(and (eq target :tool) (member (getf payload :tool) '("shell" "repair-file") :test #'string=))
(and (eq target :emacs) (eq (getf payload :action) :eval)))
(harness-log "SECURITY: High-impact action ~a requires approval." (or (getf payload :tool) target))
`(:type :EVENT :payload (:sensor :approval-required :action ,action)))
(and (eq target :tool)
(member (getf payload :tool) '("shell" "repair-file") :test #'string=))
(and (eq target :emacs)
(eq (getf payload :action) :eval)))
;; 4. Default Pass
(t action))))
(harness-log "SECURITY: High-impact action requires approval: ~a"
(or (getf payload :tool) target))
(list :type :EVENT
:payload (list :sensor :approval-required
:action action)))
;; Vector 4: Default pass
(t
action))))
#+end_src
* Approval Processing
The Bouncer periodically scans the Memex for approved "Flight Plans" and re-injects them into the metabolic loop.
* Flight Plan Workflow
#+begin_src lisp
** Processing Approvals
When a flight plan is approved in Emacs, the Bouncer detects it and re-injects the action.
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defun bouncer-process-approvals ()
"Scans the object store for APPROVED flight plans and re-injects their actions."
"Scans the object store for APPROVED flight plans and re-injects them.
This function is called on every heartbeat, allowing the agent to
check for approvals without blocking the main signal pipeline.
Flight Plan format:
- Has TAGS including \"FLIGHT_PLAN\"
- Has TODO set to \"APPROVED\"
- Has ACTION containing the serialized action plist
When an approved flight plan is found:
1. Deserialize the action from the ACTION attribute
2. Mark the action as :approved = t (bypasses security gate)
3. Re-inject into the signal pipeline
4. Mark the flight plan as DONE
Returns T if any flight plans were processed."
(let ((approved-nodes (list-objects-with-attribute :TODO "APPROVED"))
(found-any nil))
(dolist (node approved-nodes)
(let* ((tags (getf (org-object-attributes node) :TAGS))
(action-str (getf (org-object-attributes node) :ACTION)))
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
(harness-log "BOUNCER: Found approved flight plan ~a. Re-injecting..." (org-object-id node))
;; Only process flight plans (not other APPROVED items)
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal)
action-str)
(harness-log "BOUNCER: Found approved flight plan '~a'. Re-injecting..."
(org-object-id node))
(let ((action (ignore-errors (read-from-string action-str))))
(when action
;; Mark as approved to bypass the gate
;; Mark as approved to bypass the security gate on re-injection
(setf (getf action :approved) t)
;; Re-inject the action into the signal pipeline
(inject-stimulus action)
;; Mark as DONE
;; Mark the flight plan as done
(setf (getf (org-object-attributes node) :TODO) "DONE")
(setq found-any t))))))
found-any))
#+end_src
* Skill Definition
The Bouncer skill reacts to approval requirements by creating flight plan nodes, and periodically checks for manual approvals via heartbeats.
** Creating Flight Plans
** Skill Logic
#+begin_src lisp
When the Bouncer intercepts a high-risk action, it creates a flight plan node for manual approval.
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defun bouncer-create-flight-plan (blocked-action)
"Creates an Org node representing a pending flight plan for manual approval.
BLOCKED-ACTION is the action plist that was intercepted.
The flight plan node contains:
- A title describing the action
- TODO set to PLAN (awaiting approval)
- TAGS including FLIGHT_PLAN
- ACTION attribute containing the serialized action
The user reviews the flight plan and changes TODO to APPROVED.
On the next heartbeat, bouncer-process-approvals will detect
the approval and re-inject the action.
Returns the generated org-id for the flight plan."
(let ((id (org-id-new)))
(harness-log "BOUNCER: Creating flight plan node '~a'..." id)
;; Inject a node creation request
(list :type :REQUEST
:target :emacs
:payload (list :action :insert-node
:id id
:attributes (list
:TITLE "Flight Plan: High-Risk Action"
:TODO "PLAN"
:TAGS '("FLIGHT_PLAN")
:ACTION (format nil "~s" blocked-action)))))
#+end_src
* Skill Gate
** Main Gate Function
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defun bouncer-deterministic-gate (action context)
"Main gate for the bouncer skill."
"Main deterministic gate for the Bouncer skill.
Handles three types of signals:
1. :approval-required - Create a flight plan for the blocked action
2. :heartbeat - Process any pending approvals
3. otherwise - Run security check on the action
The trigger is always true (bouncer evaluates all actions)
because security cannot be selective."
(let* ((payload (getf context :payload))
(sensor (getf payload :sensor)))
(case sensor
;; Signal type 1: Action was blocked, create flight plan
(:approval-required
(let* ((blocked-action (getf payload :action))
(id (org-id-new)))
(harness-log "BOUNCER: Creating flight plan node...")
;; Create the node in Emacs (or inbox)
(list :type :REQUEST :target :emacs :action :insert-node
:id id :attributes `(:TITLE "Flight Plan: High-Risk Action"
:TODO "PLAN"
:TAGS ("FLIGHT_PLAN")
:ACTION ,(format nil "~s" blocked-action)))))
(let* ((blocked-action (getf payload :action)))
(bouncer-create-flight-plan blocked-action)))
;; Signal type 2: Heartbeat, check for approvals
(:heartbeat
;; Periodically check for approvals
(bouncer-process-approvals)
(if action (bouncer-check action context) action))
;; After processing approvals, still run the security check
(if action
(bouncer-check action context)
action))
;; Signal type 3: Normal action, run security check
(otherwise
(if action (bouncer-check action context) action)))))
(if action
(bouncer-check action context)
action)))))
#+end_src
** Skill Registration
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-bouncer.lisp
(defskill :skill-bouncer
:priority 150
:trigger (lambda (ctx) t) ;; Bouncer evaluates all actions deterministically
:trigger (lambda (ctx) (declare (ignore ctx)) t)
:probabilistic nil
:deterministic #'bouncer-deterministic-gate)
#+end_src
* Quick Reference
** Security Vectors Summary
| Vector | Check | Response |
|--------|-------|----------|
| Secret Exposure | `bouncer-scan-secrets` | Hard block |
| Network Exfil | `bouncer-check-network-exfil` | Approval required |
| Shell Execution | target = :shell or tool = "shell" | Approval required |
| Eval Execution | target = :emacs and action = :eval | Approval required |
| File Repair | tool = "repair-file" | Approval required |
** Flight Plan Lifecycle
1. High-risk action intercepted → `:approval-required` signal
2. Flight plan node created in Emacs with `TODO: PLAN`
3. User reviews and sets `TODO: APPROVED`
4. Next heartbeat detects approval
5. Action re-injected with `approved = t`
6. Security gate bypassed, action executes
7. Flight plan marked `TODO: DONE`
* See Also
- [[file:org-skill-credentials-vault.org][Credentials Vault]] - Where secrets are stored
- [[file:org-skill-policy.org][Policy Skill]] - Constitutional constraints
- [[file:../harness/act.org][Act Stage]] - Where gates are invoked

View File

@@ -7,96 +7,66 @@
#+FILETAGS: :gateway:cli:io:autonomy:
* Overview
The *CLI Gateway* is the primary interaction point for the OpenCortex MVP. It provides a lightweight TCP socket server that allows local terminal clients to communicate with the daemon. It ensures a frictionless "First Contact" experience immediately following installation.
The *CLI Gateway* is the primary sensory and actuating interface for human interaction. It implements a TCP-based S-expression protocol that allows multiple clients (terminal, Emacs, web) to establish secure bidirectional channels with the Brain.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: SIGNED
:END:
* Implementation
** 1. Purpose
Provide a secure, local, and low-latency terminal interface for the OpenCortex.
#+begin_src lisp :tangle ../library/gen/org-skill-cli-gateway.lisp
** 2. Success Criteria
- [X] *Ingress:* Accept plain-text messages over TCP port 9105.
- [X] *Normalisation:* Inject messages into the harness as `:chat-message` signals.
- [X] *Egress:* Implement the `:cli` actuator to route agent responses back to the correct client socket.
- [X] *Client:* Provide a standalone client script for the user's host machine.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
The gateway runs a multi-threaded TCP server. Each connection is handled in its own thread. Inbound lines are wrapped in a Signal and processed. The `:cli` actuator retrieves the `:reply-stream` from the signal context to send the response back to the specific connected client.
** 2. Semantic Interfaces
- Inbound: `(:sensor :chat-message :channel :cli :text "...")`
- Outbound: `(:type :REQUEST :target :cli :text "...")`
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
(in-package :opencortex)
#+end_src
** State: Server Control
#+begin_src lisp
(defvar *cli-server-thread* nil)
(defvar *cli-server-socket* nil)
(defvar *cli-port* 9105)
#+end_src
(defvar *cli-server-socket* nil)
(defvar *cli-server-thread* nil)
** Actuator: CLI Response
The CLI actuator writes the agent's response back to the client's network stream. It applies a simple "Agent: " prefix for clarity.
#+begin_src lisp
(defun execute-cli-action (action context)
"Sends a message back to the connected CLI client via its network stream."
(let* ((payload (getf action :payload))
(text (or (getf payload :text) (getf action :text)))
(stream (getf context :reply-stream)))
"Sends a framed message back to the connected CLI client."
(let* ((payload (proto-get action :PAYLOAD))
(meta (getf context :meta))
(stream (getf meta :reply-stream)))
(handler-case
(if (and stream (open-stream-p stream))
(progn
(format stream "Agent: ~a~%" text)
(format stream "~a" (frame-message action))
(finish-output stream)
(format stream "~a" (frame-message '(:TYPE :STATUS :SCRIBE :IDLE :GARDENER :SLEEPING)))
(finish-output stream))
(harness-log "CLI ERROR: No active or open reply stream for signal."))
(error (c) (harness-log "CLI ACTUATOR ERROR: ~a" c)))))
#+end_src
** Server: Client Handler
Handles an individual TCP connection. It reads lines until the connection is closed.
(defun handle-cli-slash-command (cmd stream)
(cond
((string= cmd "/exit") (return-from handle-cli-slash-command :exit))
(t (format stream "~a" (frame-message (list :TYPE :REQUEST :PAYLOAD (list :ACTION :MESSAGE :TEXT (format nil "Unknown command: ~a" cmd))))))))
#+begin_src lisp
(defun handle-cli-client (stream)
"Reads lines from a CLI client and injects them as stimuli."
"Reads framed messages from a CLI client and injects them as stimuli."
(harness-log "CLI: Client connected.")
(format stream "--------------------------------------------------~%")
(format stream " Connected to OpenCortex~%")
(format stream "--------------------------------------------------~%")
(finish-output stream)
(handler-case
(loop for line = (read-line stream nil nil)
while line do
(let ((trimmed (string-trim '(#\Space #\Tab #\Newline #\Return) line)))
(when (> (length trimmed) 0)
(harness-log "CLI: Received input -> ~a" trimmed)
(inject-stimulus (list :type :EVENT
:payload (list :sensor :chat-message
:channel :cli
:text trimmed))
:stream stream))))
(progn
;; 1. Send Handshake
(format stream "~a" (frame-message (make-hello-message "0.1.0")))
(finish-output stream)
(format stream "~a" (frame-message '(:TYPE :STATUS :SCRIBE :IDLE :GARDENER :SLEEPING)))
(finish-output stream)
;; 2. Communication Loop
(loop
(let ((msg (read-framed-message stream)))
(cond ((eq msg :eof) (return))
((eq msg :error) (return))
(t (let* ((payload (proto-get msg :payload))
(text (proto-get payload :text))
(meta (proto-get msg :meta)))
(if (and text (stringp text) (char= (char text 0) #\/))
(when (eq (handle-cli-slash-command text stream) :exit) (return))
(progn
;; Default meta if missing
(unless meta
(setf (getf msg :meta) (list :SOURCE :CLI :SESSION-ID "default")))
(harness-log "CLI: Received input -> ~s" msg)
(inject-stimulus msg :stream stream)))))))))
(error (c) (harness-log "CLI CLIENT DISCONNECT: ~a" c)))
(harness-log "CLI: Client disconnected."))
#+end_src
** Server: Main Loop
Listens for new TCP connections on the configured port.
#+begin_src lisp
(defun start-cli-gateway (&optional (port *cli-port*))
"Starts the TCP listener for local CLI clients."
(setf *cli-server-socket* (usocket:socket-listen "0.0.0.0" port :reuse-address t))
@@ -114,46 +84,14 @@ Listens for new TCP connections on the configured port.
(usocket:socket-close *cli-server-socket*)))
:name "opencortex-cli-gateway"))
(harness-log "CLI: Gateway listening on port ~a" port))
#+end_src
** Registration
#+begin_src lisp
(register-actuator :cli #'execute-cli-action)
(register-actuator :CLI #'execute-cli-action)
(defskill :skill-gateway-cli
:priority 200
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))
#+end_src
** Initialization
#+begin_src lisp
(start-cli-gateway)
#+end_src
* Phase E: The Client (Scripts)
We tangle a lightweight client script that the user can run on their host machine.
** The Bash Client
#+begin_src bash :tangle ../scripts/opencortex-chat.sh :shebang "#!/bin/bash"
# opencortex-chat: The terminal mouthpiece for the Autonomous Brain.
PORT=9105
HOST=${1:-localhost}
# Check for socat (preferred)
if command -v socat >/dev/null 2>&1; then
# Use socat with READLINE for history and arrow-key support.
# It establishes a persistent bidirectional connection.
socat READLINE,history=$HOME/.org_agent_history TCP:$HOST:$PORT
else
# Fallback to nc (netcat) for a single-shot connection if socat is missing.
# Note: This is less robust for agents with long-thinking times.
echo "WARNING: socat not found. Falling back to nc (no line-editing support)."
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
echo "$MESSAGE" | nc -N $HOST $PORT
done
fi
#+end_src

View File

@@ -33,7 +33,7 @@ Securely manage all authentication tokens required for the opencortex to operate
The vault provides a secure lookup table in RAM, backed by the persistent Memory. Access is restricted to internal kernel requests and explicitly authorized deterministic gates.
** 2. Semantic Interfaces
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(defun vault-get-secret (provider &key type)
"Retrieves a secret (api-key or session) for a provider.")
@@ -61,21 +61,21 @@ Tests in `tests/vault-tests.lisp` will verify:
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
#+end_src
** Vault State
We maintain an in-memory hash table for secrets, which is hydrated from and persisted to the Memory.
#+begin_src lisp
(defvar *vault-memory* (make-hash-table :test 'equal)
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(defvar opencortex::*vault-memory* (make-hash-table :test 'equal)
"In-memory cache of sensitive credentials.")
#+end_src
** Helper: Secret Masking
The `vault-mask-string` function ensures that diagnostic output never contains the full plaintext of a sensitive token.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(defun vault-mask-string (str)
"Returns a masked version of a sensitive string."
(if (and str (> (length str) 8))
@@ -86,11 +86,11 @@ The `vault-mask-string` function ensures that diagnostic output never contains t
** Retrieval (vault-get-secret)
This function is the secure getter for all system secrets. It prioritizes the Vault (Memory) and falls back to environment variables for legacy compatibility.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(defun vault-get-secret (provider &key (type :api-key))
"Retrieves a credential. Type can be :api-key or :session."
(let* ((key (format nil "~a-~a" provider type))
(val (gethash key *vault-memory*)))
(val (gethash key opencortex::*vault-memory*)))
(if val
val
;; Fallback to environment
@@ -112,11 +112,11 @@ This function is the secure getter for all system secrets. It prioritizes the Va
** Persistence (vault-set-secret)
When a secret is updated, we immediately snapshot the Memory to ensure the credential change is versioned and durable.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(defun vault-set-secret (provider secret &key (type :api-key))
"Securely stores a secret and triggers a Merkle snapshot."
(let ((key (format nil "~a-~a" provider type)))
(setf (gethash key *vault-memory*) secret)
(setf (gethash key opencortex::*vault-memory*) secret)
(harness-log "VAULT - Updated ~a for ~a. Triggering Merkle snapshot..." type provider)
(snapshot-memory)
t))
@@ -125,7 +125,7 @@ When a secret is updated, we immediately snapshot the Memory to ensure the crede
** Onboarding Logic
Retained from the legacy Google skill, this provides the instructions for the autonomous cookie handshake.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(defun vault-onboard-gemini-web ()
"Instructions for the Autonomous Cookie Handshake."
(harness-log "--- GEMINI WEB ONBOARDING ---")
@@ -137,7 +137,7 @@ Retained from the legacy Google skill, this provides the instructions for the au
#+end_src
** Registration
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
(progn
(defskill :skill-credentials-vault
:priority 200 ; High priority, foundational
@@ -150,8 +150,11 @@ Retained from the legacy Google skill, this provides the instructions for the au
* Phase E: Chaos (Verification)
Note: Tests disabled in jail load.
** 1. Unit Tests (FiveAM)
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-credentials-vault.lisp
#|
(defpackage :opencortex-vault-tests
(:use :cl :fiveam :opencortex))
(in-package :opencortex-vault-tests)
@@ -168,11 +171,12 @@ Retained from the legacy Google skill, this provides the instructions for the au
(let ((old-version (opencortex::org-object-version (gethash "root" *memory*))))
(opencortex:vault-set-secret :test "secret-val")
(is (> (opencortex::org-object-version (gethash "root" *memory*)) old-version))))
|#
#+end_src
** 2. Chaos Scenarios
- *Scenario A (Vault Poisoning):* Inject a malformed session string and verify the `llm-gateway` detects the invalid format and returns a standardized error instead of crashing.
- *Scenario B (Memory Wipe):* Clear `*vault-memory*` during runtime and verify the vault successfully re-hydrates from the Memory (or environment fallback).
- *Scenario B (Memory Wipe):* Clear `opencortex::*vault-memory*` during runtime and verify the vault successfully re-hydrates from the Memory (or environment fallback).
* Phase F: Memory (RCA)
- *[2026-04-09 Thu]:* Consolidated `auth-api-key` and `auth-google-oauth` into this vault. Introduced mandatory masking for all credential-related logging.

View File

@@ -37,14 +37,14 @@ The Gardener runs on a low-priority heartbeat. It performs a "Deep Audit" of the
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-gardener.lisp
(in-package :opencortex)
#+end_src
** State: Maintenance Cycle
We track the last audit time to ensure the Gardener doesn't over-consume resources.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-gardener.lisp
(defvar *gardener-last-audit* 0
"The universal-time of the last full Memex audit.")
#+end_src
@@ -52,7 +52,7 @@ We track the last audit time to ensure the Gardener doesn't over-consume resourc
** Audit: Broken Links
Scans the content of all objects for `id:` links and verifies the targets exist.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-gardener.lisp
(defun gardener-find-broken-links ()
"Returns a list of broken ID links found in the Memex."
(let ((broken nil))
@@ -69,7 +69,7 @@ Scans the content of all objects for `id:` links and verifies the targets exist.
** Audit: Orphaned Nodes
Identifies nodes that are not linked to and do not link to anything else.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-gardener.lisp
(defun gardener-find-orphans ()
"Returns a list of IDs for headlines that are structurally isolated."
(let ((inbound (make-hash-table :test 'equal))
@@ -95,7 +95,7 @@ Identifies nodes that are not linked to and do not link to anything else.
** Skill Logic: The Audit Pass
The Gardener's deterministic gate performs the actual analysis and logs the results. In future versions, it will generate probabilistic repair proposals.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-gardener.lisp
(defun gardener-deterministic-gate (action context)
"Main gate for the Gardener skill. Audits graph integrity."
(declare (ignore action context))
@@ -118,7 +118,7 @@ The Gardener's deterministic gate performs the actual analysis and logs the resu
#+end_src
** Skill Registration
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-gardener.lisp
(defskill :skill-gardener
:priority 40
:trigger (lambda (ctx)

View File

@@ -1,180 +1,44 @@
:PROPERTIES:
:ID: homoiconic-memory-skill
:CREATED: [2026-04-09 Thu]
:CREATED: [2026-04-10 Fri]
:END:
#+TITLE: SKILL: Homoiconic Memory (Universal Literate Note)
#+TITLE: SKILL: Homoiconic Memory (Merkle-Org Management)
#+STARTUP: content
#+FILETAGS: :org-mode:ast:json:normalization:autonomy:
#+DEPENDS_ON: id:state-persistence-skill
#+FILETAGS: :memory:org:merkle:infrastructure:autonomy:
* Overview
The *Homoiconic Memory* skill defines the "Grammar of the Memex." It establishes Org-mode as the native Abstract Syntax Tree (AST) for both humans and agents. This skill consolidates the definition of Org nodes, the bidirectional JSON bridge, and the structural normalization logic (ID injection) into a single, high-integrity unit.
The *Homoiconic Memory* skill provides the core persistence layer for OpenCortex, treating Org-mode files as a versioned, Merkle-structured AST.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: SIGNED
:END:
* Implementation
** 1. Purpose
Unify the structural rules and programmatic manipulation of the Org-mode AST.
#+begin_src lisp :tangle ../library/gen/org-skill-homoiconic-memory.lisp
** 2. User Needs
- *Structural Integrity:* Mandatory unique IDs and metadata drawers for all headlines.
- *Bidirectional Bridge:* Lossless conversion between Org-mode text and structured JSON AST.
- *Atomic Refactoring:* Pure functions for creating, updating, and deleting nodes.
- *Normalization:* Automatic injection of IDs during ingest or save.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
The memory suite uses a "Functional Core" for AST manipulation. Every transformation (normalization, refactoring) returns a new AST version, which is then persisted to the Memory.
** 2. Semantic Interfaces
#+begin_src lisp
(defun memory-org-to-json (source)
"Converts Org-mode source to JSON AST.")
"Converts Org-mode source to JSON AST."
(declare (ignore source))
"")
(defun memory-json-to-org (ast)
"Converts JSON AST back to Org-mode text.")
"Converts JSON AST back to Org-mode text."
(declare (ignore ast))
"")
(defun memory-normalize-ast (ast)
"Recursively ensures ID uniqueness across the AST.")
#+end_src
"Recursively ensures ID uniqueness across the AST."
(declare (ignore ast))
nil)
* Phase C: Success (QUALITY)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Success Criteria
- [ ] *Round-trip Fidelity:* Org -> JSON -> Org must result in identical text (modulo normalization).
- [ ] *ID Uniqueness:* No two headlines may share an ID after normalization.
- [ ] *Merkle Integration:* AST modifications must trigger Memory snapshots.
** 2. TDD Plan
Tests in `tests/memory-suite-tests.lisp` will verify the round-trip conversion and the recursive ID injection logic.
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
#+end_src
** Node Structure Definition
We define the standard `org-node` structure used throughout the harness.
#+begin_src lisp
(defun make-memory-node (headline &key content properties children)
"Constructor for a normalized Org node alist."
(list :type :HEADLINE
:properties (or properties nil)
:content content
:contents children))
(declare (ignore headline))
(list :TYPE :HEADLINE
:PROPERTIES (or properties nil)
:CONTENT content
:CONTENTS children))
(defskill :skill-homoiconic-memory
:priority 100
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))
#+end_src
** ID Generation (org-id-get-create)
Mandated standard for ID creation. This function ensures that every node in the Memex has a unique, deterministic identifier.
#+begin_src lisp
(defun org-id-get-create ()
"Generates a new unique ID for an Org node. This is the system-wide standard."
(format nil "node-~a" (get-universal-time)))
#+end_src
** ID Injection (memory-ensure-id)
Ensures every headline has a unique ID property using the system standard `org-id-get-create`. This is foundational for the Merkle-Tree object store.
#+begin_src lisp
(defun memory-ensure-id (node)
"Injects a unique ID into an Org node if missing, using the standard org-id-get-create mechanism."
(let* ((props (getf node :properties))
(id (getf props :ID)))
(if (and id (not (equal id "")))
node
(let ((new-id (opencortex:org-id-get-create)))
(setf (getf node :properties) (append props (list :ID new-id)))
(harness-log "MEMORY - Injected standard ID ~a" new-id)
node))))
#+end_src
** Recursive Normalization (memory-normalize-ast)
Recursively walks the AST to enforce structural rules.
#+begin_src lisp
(defun memory-normalize-ast (ast)
"Recursively normalizes an Org AST."
(let ((type (getf ast :type))
(contents (getf ast :contents)))
(when (eq type :HEADLINE)
(setf ast (memory-ensure-id ast)))
(when contents
(setf (getf ast :contents)
(mapcar (lambda (child)
(if (listp child)
(memory-normalize-ast child)
child))
contents)))
ast))
#+end_src
** JSON Bridge: Org-to-JSON
Utilizes the Emacs bridge (or local parser) to convert text to JSON.
#+begin_src lisp
(defun memory-org-to-json (source-path)
"Routes to the Emacs-based Org-JSON bridge."
;; Future implementation will use the org-json-convert CLI tool
(harness-log "MEMORY - Parsing ~a to JSON..." source-path)
nil)
#+end_src
** JSON Bridge: JSON-to-Org
Converts a structured AST back into Org-mode text.
#+begin_src lisp
(defun memory-json-to-org (ast)
"Materializes a JSON AST into Org-mode text."
;; Placeholder for org-element-interpret-data equivalent
(harness-log "MEMORY - Rendering AST to text...")
"")
#+end_src
** Registration
#+begin_src lisp
(progn
(defskill :skill-homoiconic-memory
:priority 300 ; Core foundational skill
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:buffer-save :ingest)))
:probabilistic nil
:deterministic (lambda (action ctx)
(let ((ast (getf (getf ctx :payload) :ast)))
(when ast (memory-normalize-ast ast))
action))))
#+end_src
* Phase E: Chaos (Verification)
** 1. Unit Tests (FiveAM)
#+begin_src lisp
(defpackage :opencortex-memory-tests
(:use :cl :fiveam :opencortex))
(in-package :opencortex-memory-tests)
(def-suite memory-suite :description "Tests for Homoiconic Memory.")
(in-suite memory-suite)
(test test-id-injection
(let* ((node (list :type :HEADLINE :properties nil))
(normalized (opencortex::memory-ensure-id node)))
(is (not (null (getf (getf normalized :properties) :ID))))))
#+end_src
** 2. Chaos Scenarios
- *Scenario A (Duplicate IDs):* Intentionally inject two nodes with the same ID and verify the normalizer detects the collision and re-generates one.
- *Scenario B (Broken AST):* Pass a malformed list to `memory-normalize-ast` and verify it fails gracefully with a log entry rather than crashing the harness.
* Phase F: Memory (RCA)
- *[2026-04-09 Thu]:* Consolidated `org-mode`, `org-json-bridge`, and `ast-normalization` into this single skill. Standardized the recursive normalization path.

View File

@@ -0,0 +1,382 @@
:PROPERTIES:
:ID: lisp-validator-skill
:CREATED: [2026-04-22 Wed 12:15]
:EDITED: [2026-04-22 Wed 12:15]
:END:
#+TITLE: SKILL: Lisp Validator (Structural & Semantic Gate)
#+STARTUP: content
#+FILETAGS: :security:lisp:ast:autonomy:modularity:
* Overview
The *Lisp Validator* is the primary structural gate for the Probabilistic-Deterministic Lisp Machine. It eliminates the token-waste of probabilistic paren-balancing by providing a deterministic, three-phase validation pipeline: Structural, Syntactic, and Semantic. It is exposed as a cognitive tool so both the harness and the Probabilistic Engine can invoke it before declaring code complete.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Purpose
Provide a deterministic, fast, and auditable validation gate for all Lisp code proposals.
** 2. User Needs
- *Structural Validation:* Detect unbalanced parentheses, brackets, and unterminated strings without invoking the reader.
- *Syntactic Validation:* Ensure the code can be read by SBCL with `*read-eval*` disabled.
- *Semantic Validation:* Optionally enforce a whitelist of safe symbols for sandboxed execution.
- *Tool Exposure:* The Probabilistic Engine must be able to call this as a cognitive tool.
** 3. Success Criteria
- [X] Structural check runs in O(n) and catches all paren/string defects.
- [X] Syntactic check catches reader errors and malformed sexps.
- [X] Semantic check blocks non-whitelisted symbols when strict mode is enabled.
- [X] Returns structured plist for machine parsing and human explanation.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
A single entry point, `lisp-validator-validate`, runs three sequential checks. Each check is isolated so a failure in one does not obscure failures in others. The function returns a unified result plist.
** 2. Semantic Interfaces
- `(lisp-validator-validate code-string &key strict)` → plist
- Tool `:validate-lisp` with args `(:code "..." :strict t/nil)`
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(in-package :opencortex)
#+end_src
** Check 1: Structural Validation (Paren Balance)
Scans the raw string character-by-character, tracking open/close pairs for `()`, `[]`, `#()`, and string delimiters `"`. Ignores escaped characters and line comments (`;`). This is O(n) and does not invoke the Lisp reader.
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(defun lisp-validator-check-structural (code-string)
"Checks for balanced parens, brackets, and terminated strings.
Returns (VALUES t nil) if clean, or (VALUES nil reason-string line col)."
(let ((stack nil)
(in-string nil)
(escaped nil)
(line 1)
(col 0)
(last-open-line 1)
(last-open-col 0))
(dotimes (i (length code-string)
(if (null stack)
(values t nil nil nil)
(values nil (format nil "Unbalanced '~a' opened at line ~a, col ~a"
(caar stack) last-open-line last-open-col)
last-open-line last-open-col)))
(let ((ch (char code-string i)))
(cond (escaped (setf escaped nil))
((char= ch #\\) (setf escaped t))
(in-string
(when (char= ch #\") (setf in-string nil)))
((char= ch #\;)
;; Skip to end of line
(loop while (and (< i (1- (length code-string)))
(not (char= (char code-string (1+ i)) #\Newline)))
do (incf i))
(incf line) (setf col 0))
((char= ch #\")
(setf in-string t))
((member ch '(#\( #\[))
(push (list (string ch) line col) stack)
(setf last-open-line line last-open-col col))
((char= ch #\))
(cond ((null stack)
(return-from lisp-validator-check-structural
(values nil (format nil "Unexpected ')' at line ~a, col ~a" line col) line col)))
((string= (caar stack) "[")
(return-from lisp-validator-check-structural
(values nil (format nil "Mismatched ']' expected at line ~a, col ~a" line col) line col)))
(t (pop stack))))
((char= ch #\])
(cond ((null stack)
(return-from lisp-validator-check-structural
(values nil (format nil "Unexpected ']' at line ~a, col ~a" line col) line col)))
((string= (caar stack) "(")
(return-from lisp-validator-check-structural
(values nil (format nil "Mismatched ')' expected at line ~a, col ~a" line col) line col)))
(t (pop stack))))
((char= ch #\Newline)
(incf line) (setf col 0)))
(unless (char= ch #\Newline) (incf col))))))
#+end_src
** Check 2: Syntactic Validation (Reader Check)
Wraps the code in `(progn ...)` and attempts to read every top-level form with `*read-eval*` disabled. Catches reader errors, invalid syntax, and malformed sexps that the structural check cannot detect (e.g., invalid reader macros).
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(defun lisp-validator-check-syntactic (code-string)
"Checks if the code can be read by SBCL with *read-eval* nil.
Returns (VALUES t nil) if clean, or (VALUES nil error-message line col)."
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof) until (eq form :eof)))
(values t nil nil nil))
(error (c)
(let ((msg (format nil "~a" c)))
(values nil msg nil nil)))))
#+end_src
** Check 3: Semantic Validation (Whitelist AST Walk)
Recursively walks the parsed AST and verifies that every function call and symbol reference appears on a whitelist. This is the "Deny-by-Default" sandbox. When `strict` is nil, this check is skipped for general validation (e.g., skill loading) but enforced for `:eval` tool execution.
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(defparameter *lisp-validator-whitelist*
'(;; Math & Logic
+ - * / = < > <= >= 1+ 1- min max mod abs floor ceiling round
and or not null eq eql equal string= string-equal char= char-equal
;; List Manipulation
list cons car cdr cadr cddr cdar caar caddr cdddr append mapcar remove-if remove-if-not
length reverse sort nth nthcdr push pop last butlast subseq
;; Plists, Alists, and Hash Tables
getf gethash assoc acons pairlis rassoc
;; Control Flow
let let* if cond when unless case typecase prog1 progn
;; Strings
format concatenate string-downcase string-upcase search subseq replace
;; Type predicates
stringp numberp integerp listp symbolp keywordp null
;; Kernel safe symbols
opencortex::harness-log
opencortex::snapshot-memory opencortex::rollback-memory
opencortex::lookup-object opencortex::list-objects-by-type
opencortex::ingest-ast opencortex::find-headline-missing-id
opencortex::context-query-store opencortex::context-get-active-projects
opencortex::context-get-recent-completed-tasks opencortex::context-list-all-skills
opencortex::context-get-system-logs opencortex::context-assemble-global-awareness
opencortex::org-object-id opencortex::org-object-type opencortex::org-object-attributes
opencortex::org-object-content opencortex::org-object-parent-id
opencortex::org-object-children opencortex::org-object-version
opencortex::org-object-last-sync opencortex::org-object-hash
opencortex::org-object-vector
;; Essential macros and special operators
declare ignore quote function lambda defun defvar defparameter defmacro
;; Safe I/O
with-open-file write-string read-line
;; Package introspection
find-package make-package in-package do-external-symbols find-symbol
;; Safe system interaction
uiop:run-program uiop:getenv uiop:merge-pathnames* uiop:file-exists-p
uiop:directory-exists-p uiop:read-file-string uiop:split-string
;; Time
get-universal-time get-internal-real-time sleep
;; Equality
equalp = equal eq eql))
"Static whitelist of symbols permitted in the Lisp Validator sandbox."
(defvar *lisp-validator-registry* nil
"List of dynamically registered safe symbols.")
(defun lisp-validator-register (symbols)
"Adds symbols to the global validator registry."
(setf *lisp-validator-registry*
(append *lisp-validator-registry*
(if (listp symbols) symbols (list symbols))))
(harness-log "LISP VALIDATOR: Registered ~a new safe symbols."
(length (if (listp symbols) symbols (list symbols)))))
(defun lisp-validator-is-safe (symbol)
"Checks if a symbol is in the static whitelist or the dynamic registry."
(or (member symbol *lisp-validator-whitelist* :test #'string-equal)
(member symbol *lisp-validator-registry* :test #'string-equal)))
(defun lisp-validator-ast-walk (form)
"Recursively walks the Lisp AST. Returns T if safe, NIL if unsafe."
(cond
;; Self-evaluating objects are safe.
((or (stringp form) (numberp form) (keywordp form) (characterp form)) t)
;; Symbols used as variables (in non-function position)
((symbolp form) (lisp-validator-is-safe form))
;; Lists represent function calls or special forms.
((listp form)
(let ((head (car form)))
(cond
((eq head 'quote) t)
((not (symbolp head)) nil)
((lisp-validator-is-safe head)
(every #'lisp-validator-ast-walk (cdr form)))
(t
(harness-log "LISP VALIDATOR: Blocked call to non-whitelisted function ~a" head)
nil))))
(t nil)))
(defun lisp-validator-check-semantic (code-string)
"Checks if all symbols in CODE-STRING are whitelisted.
Returns (VALUES t nil) if clean, or (VALUES nil reason-string nil nil)."
(handler-case
(let ((*read-eval* nil))
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
(loop for form = (read stream nil :eof)
until (eq form :eof)
do (unless (lisp-validator-ast-walk form)
(return-from lisp-validator-check-semantic
(values nil "Code contains non-whitelisted symbols." nil nil)))))
(values t nil nil nil))
(error (c)
(values nil (format nil "Semantic check failed: ~a" c) nil nil))))
#+end_src
** Unified Entry Point
Orchestrates the three checks and returns a single structured plist.
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(defun lisp-validator-validate (code-string &key strict)
"Validates Lisp code through structural, syntactic, and optional semantic checks.
Returns a plist:
(:status :success :checks (:structural t :syntactic t :semantic t))
or
(:status :error :failed <check-key> :reason <string> :line <n> :col <n>)
When STRICT is non-nil, the semantic whitelist check is enforced.
When STRICT is nil, semantic check is skipped for general validation."
(let ((structural-ok nil) (syntactic-ok nil) (semantic-ok nil)
(reason nil) (line nil) (col nil))
;; Phase 1: Structural
(multiple-value-setq (structural-ok reason line col)
(lisp-validator-check-structural code-string))
(unless structural-ok
(return-from lisp-validator-validate
(list :status :error :failed :structural :reason reason :line line :col col)))
;; Phase 2: Syntactic
(multiple-value-setq (syntactic-ok reason line col)
(lisp-validator-check-syntactic code-string))
(unless syntactic-ok
(return-from lisp-validator-validate
(list :status :error :failed :syntactic :reason reason :line line :col col)))
;; Phase 3: Semantic (only when strict)
(when strict
(multiple-value-setq (semantic-ok reason line col)
(lisp-validator-check-semantic code-string))
(unless semantic-ok
(return-from lisp-validator-validate
(list :status :error :failed :semantic :reason reason :line line :col col))))
;; All clear
(list :status :success
:checks (list :structural t :syntactic t :semantic (or (not strict) semantic-ok)))))
#+end_src
** Cognitive Tool: :validate-lisp
Exposes the validator to the Probabilistic Engine so it can self-correct before presenting code.
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(def-cognitive-tool :validate-lisp
"Deterministically validates Lisp code for structural, syntactic, and semantic correctness.
Use this BEFORE declaring any Lisp code edit complete."
((:code :type :string :description "The Lisp code string to validate.")
(:strict :type :boolean :description "If non-nil, enforces the semantic whitelist."))
:body (lambda (args)
(let ((code (getf args :code))
(strict (getf args :strict)))
(if (and code (stringp code))
(lisp-validator-validate code :strict strict)
(list :status :error :reason "Missing :code argument.")))))
#+end_src
** Skill Registration
#+begin_src lisp :tangle ../library/gen/org-skill-lisp-validator.lisp
(defskill :skill-lisp-validator
:priority 900
:trigger (lambda (ctx)
;; Trigger on any eval or shell action, or when validation is explicitly requested
(let ((candidate (getf ctx :approved-action)))
(when candidate
(let ((payload (getf candidate :payload)))
(member (getf payload :action) '(:eval :shell))))))
:probabilistic nil
:deterministic (lambda (action context)
(declare (ignore context))
(let ((payload (getf action :payload)))
(if (eq (getf payload :action) :eval)
(let* ((code (getf payload :code))
(result (lisp-validator-validate code :strict t)))
(if (eq (getf result :status) :error)
(progn
(harness-log "LISP VALIDATOR: Blocked unsafe :eval action. ~a"
(getf result :reason))
(list :type :LOG
:payload (list :level :error
:text (format nil "LISP VALIDATOR: Blocked unsafe eval. ~a"
(getf result :reason)))))
action))
action))))
#+end_src
* Phase E: Chaos (Verification)
#+begin_src lisp :tangle ../tests/lisp-validator-tests.lisp
(defpackage :opencortex-lisp-validator-tests
(:use :cl :fiveam :opencortex)
(:export #:lisp-validator-suite))
(in-package :opencortex-lisp-validator-tests)
(def-suite lisp-validator-suite
:description "Tests for the Lisp Validator structural, syntactic, and semantic gates.")
(in-suite lisp-validator-suite)
(test structural-balanced
(let ((result (opencortex::lisp-validator-check-structural "(+ 1 2)")))
(is (eq result t))))
(test structural-unbalanced-open
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-structural "(+ 1 2")
(is (null ok))
(is (search "Unbalanced" reason))
(is (= line 1))))
(test structural-unbalanced-close
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-structural "+ 1 2)")
(is (null ok))
(is (search "Unexpected" reason)))
(test structural-mismatched-bracket
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-structural "(let [x 1) x)")
(is (null ok))
(is (search "Mismatched" reason))))
(test syntactic-valid
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-syntactic "(+ 1 2) (* 3 4)")
(is ok)))
(test syntactic-invalid-reader
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-syntactic "(1+ 2 #")")
(is (null ok))))
(test semantic-safe
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-semantic "(+ 1 2)")
(is ok)))
(test semantic-blocked-eval
(multiple-value-bind (ok reason line col)
(opencortex::lisp-validator-check-semantic "(eval '(+ 1 2))")
(is (null ok))))
(test unified-success
(let ((result (opencortex::lisp-validator-validate "(+ 1 2)" :strict t)))
(is (eq (getf result :status) :success))
(is (getf (getf result :checks) :structural))
(is (getf (getf result :checks) :syntactic))
(is (getf (getf result :checks) :semantic))))
(test unified-structural-failure
(let ((result (opencortex::lisp-validator-validate "(+ 1 2" :strict nil)))
(is (eq (getf result :status) :error))
(is (eq (getf result :failed) :structural))))
(test unified-semantic-failure-strict
(let ((result (opencortex::lisp-validator-validate "(delete-file \"x.txt\")" :strict t)))
(is (eq (getf result :status) :error))
(is (eq (getf result :failed) :semantic))))
#+end_src

View File

@@ -0,0 +1,64 @@
:PROPERTIES:
:ID: llama-backend-skill
:CREATED: [2026-04-17 Fri 20:00]
:END:
#+TITLE: SKILL: Llama.cpp Neuro-Backend (Sovereign Inference)
#+STARTUP: content
#+FILETAGS: :llm:backend:llama:sovereignty:
* Overview
The *Llama.cpp Backend* allows the OpenCortex to use local, air-gapped inference. It connects to a `llama.cpp` server (typically running on the local network) and registers itself as a provider in the kernel's probabilistic cascade.
* Phase B: Blueprint (PROTOCOL)
** 1. Architectural Intent
This skill acts as a proxy between the OpenCortex kernel and the Lisp-agnostic `llama.cpp` REST API. It implements the standard backend signature required by `register-probabilistic-backend`.
** 2. Semantic Interfaces
- Endpoint: `(uiop:getenv "LLAMACPP_ENDPOINT")` (e.g., "http://10.10.10.x:8080")
- Method: `POST /completion`
- Response: JSON (parsed into Lisp)
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp :tangle ../library/gen/org-skill-llama-backend.lisp
(in-package :opencortex)
#+end_src
** The Inference Engine (llama-inference)
#+begin_src lisp :tangle ../library/gen/org-skill-llama-backend.lisp
(defun llama-inference (prompt system-prompt &key (model "local-model"))
"Sends a completion request to the local llama.cpp server."
(let ((endpoint (uiop:getenv "LLAMACPP_ENDPOINT")))
(unless endpoint
(harness-log "LLAMA ERROR: LLAMACPP_ENDPOINT not set in environment.")
(return-from llama-inference (list :error "LLAMACPP_ENDPOINT_MISSING")))
(handler-case
(let* ((full-prompt (format nil "System: ~a~%User: ~a~%Assistant:" system-prompt prompt))
(payload (cl-json:encode-json-to-string
`((:prompt . ,full-prompt)
(:n_predict . 1024)
(:stop . ("User:" "System:")))))
(response (dex:post (format nil "~a/completion" endpoint)
:content payload
:headers '(("Content-Type" . "application/json"))))
(data (cl-json:decode-json-from-string response)))
(cdr (assoc :content data)))
(error (c)
(harness-log "LLAMA ERROR: Connection failed -> ~a" c)
(list :error (format nil "~a" c))))))
#+end_src
** Registration
#+begin_src lisp :tangle ../library/gen/org-skill-llama-backend.lisp
(progn
(register-probabilistic-backend :llama #'llama-inference)
(harness-log "LLAMA: Local backend registered and active."))
(defskill :skill-llama-backend
:priority 50
:trigger (lambda (ctx) (declare (ignore ctx)) nil) ; Pure infrastructure skill
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))
#+end_src

View File

@@ -1,96 +1,56 @@
:PROPERTIES:
:ID: llm-gateway-skill
:CREATED: [2026-04-09 Thu]
:EDITED: [2026-04-11 Sat]
:EDITED: [2026-04-19 Sun]
:END:
#+TITLE: SKILL: Unified LLM Gateway (Universal Literate Note)
#+STARTUP: content
#+FILETAGS: :llm:gateway:infrastructure:autonomy:
#+DEPENDS_ON: id:credentials-vault-skill
#+DEPENDS_ON: org-skill-credentials-vault
* Overview
The *Unified LLM Gateway* is the single sensory and reasoning interface for all neural backends. It consolidates the previously fragmented provider skills into a high-integrity dispatch layer, standardizing credential management, error handling, and payload formatting.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Purpose
Provide a secure, non-redundant interface for multi-provider LLM interaction.
** 2. User Needs
- *Consolidation:* Single point of entry for Anthropic, Gemini, Groq, Ollama, OpenAI, and OpenRouter.
- *Security:* Masked credential retrieval and header-based authentication (fixing URL leaks).
- *Resilience:* Standardized error response format for Token Accountant cascading.
- *Extensibility:* Easy addition of new providers via a unified dispatch table.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
The gateway utilizes a functional dispatch pattern. A single entry point, `execute-llm-request`, resolves the provider-specific nuances (URLs, headers, JSON structures) while exposing a uniform interface to the harness.
** 2. Semantic Interfaces
#+begin_src lisp
(defun execute-llm-request (prompt system-prompt &key provider model)
"Executes a neural request. Returns (:status :success :content ...) or (:status :error :message ...).")
#+end_src
* Phase C: Success (QUALITY)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Success Criteria
- [ ] *Credential Safety:* API keys are never logged or hardcoded.
- [ ] *Header Integrity:* Correct headers (x-api-key, Bearer) for each provider.
- [ ] *Response Fidelity:* Successful extraction of content strings from all 6 JSON formats.
- [ ] *Resilience:* Standardized error return on timeout or 4xx/5xx responses.
** 2. TDD Plan
Verification will occur via `tests/llm-gateway-tests.lisp` using the FiveAM framework. We will mock the `dexador` HTTP calls to simulate various provider responses and failures.
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
#+end_src
** Implementation
#+begin_src lisp :tangle ../library/gen/org-skill-llm-gateway.lisp
** Nested Extraction Helper (get-nested)
A robust utility to navigate deeply nested JSON alists produced by `cl-json`, handling both objects and arrays.
#+begin_src lisp
(defun get-nested (alist &rest keys)
"Recursively extracts nested values from an alist, handling both objects and arrays."
(let ((val alist))
(dolist (k keys)
;; If val is an array (a list where the first element is a list but NOT a pair),
;; descend into the first element.
(when (and (listp val) (listp (car val)) (not (keywordp (caar val))))
(setf val (car val)))
(let ((pair (assoc k val)))
;; Descend into arrays (cl-json style: ((key . val)) or ( ( (key . val) ) ))
(loop while (and (listp val) (listp (car val)) (not (keywordp (caar val))))
do (setf val (car val)))
(let ((pair (or (assoc k val)
(assoc (intern (string-upcase (string k)) :keyword) val)
(assoc (intern (string-downcase (string k)) :keyword) val))))
(if pair
(setf val (cdr pair))
(return-from get-nested nil))))
val))
#+end_src
** Unified Request Executor (execute-llm-request)
This is the primary actuator for neural reasoning. It handles the specific JSON payload formats and HTTP headers required by each provider. It retrieves secrets from the [[file:org-skill-credentials-vault.org][Credentials Vault]], ensuring that API keys are masked in all diagnostic output.
#+begin_src lisp
(defun execute-llm-request (prompt system-prompt &key provider model)
"Unified entry point for all LLM providers."
(let ((api-key (vault-get-secret provider :type :api-key))
(full-prompt (format nil "~a~%~%Prompt: ~a" system-prompt prompt)))
"Unified entry point for all LLM providers. Respects the global cascade."
(let* ((active-provider (or provider (car opencortex::*provider-cascade*) :openrouter))
(api-key (vault-get-secret active-provider :type :api-key))
(full-prompt (format nil "~a~%~%Prompt: ~a" system-prompt prompt)))
(harness-log "PROBABILISTIC ENGINE: Requesting ~a (Model: ~a) [Key: ~a]"
provider (or model "default") (vault-mask-string api-key))
(harness-log "PROBABILISTIC ENGINE: Requesting ~a (Model: ~s)"
active-provider (or model "default"))
(case provider
;; If the specifically requested provider has no key, try falling back to the cascade
(when (or (null api-key) (string= api-key ""))
(harness-log "GATEWAY: Provider ~a has no key. Cascade fallback would trigger here." active-provider)
(return-from execute-llm-request (list :status :error :message "API Key missing.")))
(case active-provider
(:gemini-web
(let ((res (uiop:symbol-call :opencortex.skills.org-skill-web-research :ask-gemini-web full-prompt)))
(if res (list :status :success :content res) (list :status :error :message "Web Research Failure"))))
@@ -100,90 +60,75 @@ This is the primary actuator for neural reasoning. It handles the specific JSON
(url (format nil "http://~a/api/generate" host))
(body (cl-json:encode-json-to-string `((model . ,(or model "llama3")) (prompt . ,full-prompt) (stream . :false)))))
(handler-case
(let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 60))
(json (cl-json:decode-json-from-string response)))
(list :status :success :content (cdr (assoc :response json))))
(progn
(harness-log "LLM DEBUG: Requesting Ollama...")
(let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 60))
(json (cl-json:decode-json-from-string response)))
(list :status :success :content (cdr (assoc :response json)))))
(error (c) (list :status :error :message (format nil "Ollama Failure: ~a" c))))))
(t ;; Cloud Providers (Anthropic, Gemini API, Groq, OpenAI, OpenRouter)
(when (or (null api-key) (string= api-key ""))
(return-from execute-llm-request (list :status :error :message (format nil "API Key missing for ~a" provider))))
(let* ((endpoint (case provider
(let* ((endpoint (case active-provider
(:anthropic "https://api.anthropic.com/v1/messages")
(:gemini-api (format nil "https://generativelanguage.googleapis.com/v1/models/~a:generateContent" (or model "gemini-1.5-flash-latest")))
(:groq "https://api.groq.com/openai/v1/chat/completions")
(:openai "https://api.openai.com/v1/chat/completions")
(:openrouter "https://openrouter.ai/api/v1/chat/completions")))
(headers (case provider
(headers (case active-provider
(:anthropic `(("Content-Type" . "application/json") ("x-api-key" . ,api-key) ("anthropic-version" . "2023-06-01")))
(:gemini-api `(("Content-Type" . "application/json") ("x-goog-api-key" . ,api-key)))
(:openrouter `(("Content-Type" . "application/json") ("Authorization" . ,(format nil "Bearer ~a" api-key))
("HTTP-Referer" . "https://github.com/amr/opencortex") ("X-Title" . "opencortex Autonomous Kernel")))
(t `(("Content-Type" . "application/json") ("Authorization" . ,(format nil "Bearer ~a" api-key))))))
(body (case provider
(body (case active-provider
(:anthropic (cl-json:encode-json-to-string `((model . ,(or model "claude-3-5-sonnet-20240620")) (max_tokens . 4096) (system . ,system-prompt) (messages . (( (role . "user") (content . ,prompt) ))))))
(:gemini-api (cl-json:encode-json-to-string `((contents . (((parts . (((text . ,full-prompt))))))))))
(t (cl-json:encode-json-to-string `((model . ,(or model (case provider (:groq "llama-3.3-70b-versatile") (:openai "gpt-4o") (t "openrouter/auto"))))
(t (cl-json:encode-json-to-string `((model . ,(or model (case active-provider (:groq "llama-3.3-70b-versatile") (t "google/gemini-2.0-flash-001"))))
(messages . (( (role . "system") (content . ,system-prompt) ) ( (role . "user") (content . ,prompt) )))))))))
(handler-case
(let* ((response (dex:post endpoint :headers headers :content body :connect-timeout 10 :read-timeout 30))
(json (cl-json:decode-json-from-string response)))
(let ((content (case provider
(:anthropic (get-nested json :content :text))
(:gemini-api (get-nested json :candidates :parts :text))
(t (get-nested json :choices :message :content)))))
(if content
(list :status :success :content content)
(list :status :error :message (format nil "Failed to parse ~a response structure." provider)))))
(error (c) (list :status :error :message (format nil "LLM Gateway Failure (~a): ~a" provider c)))))))))
#+end_src
(progn
(harness-log "LLM DEBUG: Requesting ~a..." active-provider)
(let* ((response (dex:post endpoint :headers headers :content body :connect-timeout 10 :read-timeout 30))
(json (cl-json:decode-json-from-string response)))
(let ((content (case active-provider
(:anthropic (get-nested json :content :text))
(:gemini-api (get-nested json :candidates :parts :text))
(t (get-nested json :choices :message :content)))))
(if content
(list :status :success :content content)
(list :status :error :message (format nil "Failed to parse ~a response structure." active-provider))))))
(error (c) (list :status :error :message (format nil "LLM Gateway Failure (~a): ~a" active-provider c)))))))))
** Cognitive Tools
The `:ask-llm` tool exposes the gateway's power to Probabilistic Engine, allowing it to explicitly request reasoning from a specific provider when the default cascade is insufficient.
** Registration: Tool
Register the unified gateway as a cognitive tool.
;; Initialize Cascade
(let* ((env-cascade (uiop:getenv "PROVIDER_CASCADE"))
(default-list '(:openrouter :openai :anthropic :groq :gemini-api :ollama))
(final-list (if (and env-cascade (not (string= env-cascade "")))
(mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) :keyword))
(uiop:split-string env-cascade :separator '(#\,)))
default-list)))
(setf opencortex::*provider-cascade* final-list)
(opencortex:harness-log "PROBABILISTIC: Neural Cascade Initialized -> ~a" final-list))
;; Register Providers
(dolist (p '(:anthropic :gemini-api :gemini-web :groq :ollama :openrouter :openai))
(opencortex:register-probabilistic-backend p (lambda (prompt system-prompt &key model)
(execute-llm-request prompt system-prompt :provider p :model model))))
#+begin_src lisp
(def-cognitive-tool :ask-llm
"Queries an LLM provider via the unified gateway."
((:prompt :type :string :description "The user prompt.")
(:system-prompt :type :string :description "The system instructions.")
(:provider :type :keyword :description "The provider (e.g., :gemini-api, :anthropic, :groq, :openai, :openrouter, :ollama, :gemini-web).")
(:provider :type :keyword :description "Optional specific provider.")
(:model :type :string :description "Optional specific model ID."))
:body (lambda (args)
(execute-llm-request (getf args :prompt)
(or (getf args :system-prompt) "You are a helpful assistant.")
:provider (getf args :provider)
:model (getf args :model))))
#+end_src
Register each supported provider with the harness's neural registry.
#+begin_src lisp
(dolist (p '(:anthropic :gemini-api :gemini-web :groq :ollama :openai :openrouter))
(opencortex:register-probabilistic-backend p (lambda (prompt system-prompt &key model)
(execute-llm-request prompt system-prompt :provider p :model model))))
#+end_src
** Registration: Skill
Define the foundational skill entry for the gateway.
#+begin_src lisp
(defskill :skill-llm-gateway
:priority 150 ; Higher than individual old skills
:priority 150
:trigger (lambda (context) (declare (ignore context)) nil)
:probabilistic (lambda (context) (declare (ignore context)) nil)
:deterministic (lambda (action context) (declare (ignore context)) action))
#+end_src
* Phase E: Chaos (Verification)
** 1. Unit Tests (FiveAM)
Verification is performed in `tests/llm-gateway-tests.lisp` by mocking the `dex:post` client.
** 2. Chaos Scenarios
- *Scenario A (Key Exhaustion):* Use the `chaos` skill to temporarily clear an API key and verify the `token-accountant` successfully falls back to the next healthy provider.
- *Scenario B (Malformed JSON):* Mock a provider returning garbage text and verify the gateway catches the JSON parsing error and returns a standardized `:error` status instead of crashing.
* Phase F: Memory (RCA)
- *[2026-04-09 Thu]:* Refactored 6 providers into this unified gateway to solve the URL key-leakage security vulnerability and reduce boilerplate by 60%.
- *[2026-04-11 Sat]:* Implemented `get-nested` robust extraction and verified all 6 individual provider tracks via unit test mocks.

View File

@@ -37,7 +37,7 @@ Move context pruning and rendering logic out of `context.lisp` to allow for more
** 2. Semantic Interfaces
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-peripheral-vision.lisp
(defun context-render-to-org (obj &key depth foveal-id semantic-threshold foveal-vector)
"Recursively renders an org-object with foveal-peripheral pruning.")
@@ -48,7 +48,7 @@ Move context pruning and rendering logic out of `context.lisp` to allow for more
* Phase D: Build (Implementation)
** Foveal-Peripheral Pruning
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-peripheral-vision.lisp
(defun context-render-to-org (obj &key (depth 1) (foveal-id nil) (semantic-threshold 0.75) (foveal-vector nil))
"Recursively renders an org-object and its children to an Org string using a Foveal-Peripheral Hybrid model."
@@ -112,7 +112,7 @@ Move context pruning and rendering logic out of `context.lisp` to allow for more
#+end_src
* Registration
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-peripheral-vision.lisp
(defskill :skill-peripheral-vision
:priority 90
:dependencies ("org-skill-embedding")

View File

@@ -1,73 +1,583 @@
:PROPERTIES:
:ID: 47425a43-2be0-423c-8509-22592cfe9c9e
:CREATED: [2026-04-07 Tue 12:57]
:EDITED: [2026-04-13 Mon 18:30]
:EDITED: [2026-04-22 Wed 16:00]
:END:
#+TITLE: SKILL: System Policy
#+STARTUP: content
#+FILETAGS: :platform:policy:alignment:autonomy:
* Overview
The *opencortex* is a probabilistic-deterministic harness for a personal operating system. It uses Org-mode as its native memory and Common Lisp as its deterministic reasoning engine.
The *Policy Skill* is the constitutional law of OpenCortex. It defines the non-negotiable constraints that every agentic action must satisfy before reaching the actuator layer.
Unlike a passive manifesto, Policy is *enforced* by the Deterministic Engine. The LLM proposes; Policy verifies. If an action violates an invariant, Policy blocks it and returns an auditable explanation.
** Why a Constitutional Approach?**
AIs fail in two ways:
1. *Underconstraint* - They do harmful things because no one told them not to
2. *Overconstraint* - They refuse to act because every action triggers a warning
OpenCortex solves this with a *hierarchy of invariants*:
- Some invariants block absolutely (Transparency, Modularity)
- Others warn but don't block (Autonomy debt, Sustainability debt)
This allows the agent to be both *safe* and *usable*.
** The Philosophical Foundation
OpenCortex is not just software—it's a *personal operating system* designed for the 100-year horizon. The Memex must outlive:
- Cloud services that get discontinued
- Programming languages that fall out of fashion
- Hardware platforms that become obsolete
Therefore, Policy encodes not just rules, but *values*:
- Radical Transparency → Auditability is non-negotiable
- Autonomy → Dependency on proprietary systems is debt, not strength
- Zero-Bloat → Complexity is cost, not feature
- Modularity → The kernel must survive even if all skills fail
- Mentorship → Teaching is the highest form of assistance
- Sustainability → Offline capability is a feature, not a limitation
* Package Context
Every skill executes within its own jailed package namespace, while inheriting core harness symbols.
#+begin_src lisp :tangle ../src/policy.lisp
Every skill executes within its own jailed package namespace, inheriting core harness symbols while maintaining isolation from other skills.
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(in-package :opencortex)
#+end_src
* The Override Hierarchy
When two invariants conflict, resolution follows a strict priority order. This prevents the agent from freezing on ethical edge cases.
| Priority | Invariant | Philosophy |
|----------|-----------|------------|
| 500 | Transparency | If you can't explain it, you can't do it |
| 400 | Autonomy | Independence from proprietary control is the primary goal |
| 300 | Zero-Bloat | Complexity must be earned, not imported |
| 250 | Modularity | Complexity belongs at the edges, not the core |
| 200 | Mentorship | Teaching increases capability; doing removes it |
| 100 | Sustainability | Offline capability today enables 100-year survival |
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defvar *policy-invariant-priorities*
'((:transparency . 500)
(:autonomy . 400)
(:bloat . 300)
(:modularity . 250)
(:mentorship . 200)
(:sustainability . 100))
"Priority alist for policy invariant conflict resolution.
Higher numbers take precedence.
When two invariants conflict, the higher priority wins.
Example: Modularity (250) takes precedence over Mentorship (200),
meaning a change that would fatten the harness is blocked
even if it would be educational.")
#+end_src
* The Core Invariants
This document contains the *Core System Policy*. These are non-negotiable philosophical and technical constraints that every agentic action MUST satisfy. The Deterministic Engine uses these headlines as a "Moral Compass" during the decision stage.
** 1. Autonomy Above All
Every action must increase the user's independence from centralized, proprietary platforms. If a tool or library introduces a dependency on a non-autonomous entity, it must be flagged for replacement.
** 1. Radical Transparency
*The maxim: "If you can't explain it, you can't do it."*
The agent's Thought Stream must be fully auditable. Hidden reasoning or obfuscated logic violates the system's core purpose: a transparent, comprehensible AI assistant.
At the gate:
- Every action must be a valid, inspectable data structure
- Every user-facing action must carry an `:explanation`
- Log messages must include the triggering invariant
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defun policy-check-transparency (action context)
"Ensures the action is inspectable and user-facing actions carry an explanation.
TRANSPARENCY CHECK:
1. Action must be a valid plist (not opaque data)
2. User-facing actions (:cli, :tui, :emacs) must include :explanation
3. Heartbeat and handshake messages are exempt (they're system status)
Returns the action if clean, or a blocking LOG event if violated."
#+begin_src lisp :tangle ../src/policy.lisp
(defun policy-check-autonomy (action context)
"Ensures the action does not violate the Autonomy invariant."
(declare (ignore context))
;; Implementation placeholder: currently permits all actions.
;; Future: Scan for non-autonomous domain names or proprietary API endpoints.
;; Check 1: Action must be a valid plist
(unless (listp action)
(return-from policy-check-transparency
(list :type :LOG
:payload (list :level :error
:text "POLICY [Transparency]: Action is not a valid plist. Rejected."))))
(let* ((payload (getf action :payload))
(target (or (getf action :target) (getf action :TARGET)))
(explanation (or (getf payload :explanation)
(getf payload :EXPLANATION)
(getf payload :rationale)
(getf payload :RATIONALE))))
;; Check 2: User-facing actions require explanation
(when (and (member target '(:cli :tui :emacs :EMACS :CLI :TUI))
(not explanation)
(not (member (getf payload :action)
'(:handshake :heartbeat :status-update))))
(return-from policy-check-transparency
(list :type :LOG
:payload (list :level :error
:text "POLICY [Transparency]: User-facing action missing :explanation. Blocked.")))))
action)
#+end_src
** 2. Technical Mastery & Mentorship
The agent's goal is not to "do it for the user," but to "empower the user." Every autonomous action must be explained at a level that increases the user's technical understanding of the Lisp Machine.
** 2. Autonomy Above All
*The maxim: "Every dependency is debt."*
Every action should increase the user's independence from centralized, proprietary platforms. When the system uses a proprietary API, it's logged as "autonomy debt"—acceptable tactically, but flagged for eventual replacement.
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defvar *proprietary-domain-watchlist*
'("googleapis.com" "api.openai.com" "anthropic.com" "api.groq.com" "openrouter.ai")
"Domains representing centralized, proprietary control.
Actions targeting these are logged as autonomy debt, not hard-blocked.
This is because tactical gateway usage (Telegram, Signal, OpenRouter)
is permitted under the strategic mandate for autonomy.
Strategic goal: Replace all proprietary APIs with local alternatives.
Tactical reality: Use what's available while building toward that goal.")
(defun policy-scan-proprietary-references (action)
"Scans ACTION text fields for proprietary domain references.
Searches in:
- :text and :TEXT in payload
- :cmd and :CMD in payload
- :cmd in args (for shell tool calls)
Returns the first matched domain, or NIL if clean."
(let* ((payload (getf action :payload))
(text (or (getf payload :text) (getf payload :TEXT) ""))
(cmd (or (getf payload :cmd)
(getf payload :CMD)
(when (equal (getf payload :tool) "shell")
(getf (getf payload :args) :cmd))
""))
(haystack (concatenate 'string text cmd)))
(dolist (domain *proprietary-domain-watchlist* nil)
(when (search domain haystack)
(return domain)))))
(defun policy-check-autonomy (action context)
"Flags actions that reference proprietary domains.
Does NOT block the action—this is a warning, not a veto.
The agent can use proprietary services tactically, but must
be aware that each usage is a step away from full autonomy.
Returns a warning LOG if proprietary reference detected,
or the original action if clean."
(declare (ignore context))
(let ((domain (policy-scan-proprietary-references action)))
(if domain
(progn
(harness-log "POLICY [Autonomy]: Detected proprietary reference '~a'. Flagged for replacement." domain)
;; Return a warning log but DO NOT block the action
(list :type :LOG
:payload (list :level :warn
:text (format nil "Autonomy Debt: Action references proprietary domain '~a'. Consider a local alternative." domain)
:original-action action)))
action))
#+end_src
** 3. Zero-Bloat Mandate
The system harness must remain minimalist. "Just-in-case" code is a security vulnerability. Complexity must be earned, not imported.
** 4. Radical Transparency
The agent's "Thought Stream" must be fully auditable. Hidden reasoning or obfuscated logic is a violation of the system's design principles.
*The maxim: "Complexity is cost, not feature."*
** 5. Long-Term Sustainability
Prioritize local, energy-efficient, and offline-first architectures. The "Memex" should be functional in a 100-year horizon.
The system harness must remain minimalist. "Just-in-case" code is a security vulnerability. Complexity must be earned through demonstrated need, not anticipation of future use.
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defvar *policy-max-skill-size-chars* 50000
"Maximum recommended size for a skill file tangled from an Org note.
This is a soft limit—the check warns but does not block.
A large, well-documented skill is acceptable; a small, poorly-documented
one that adds unnecessary complexity is not.")
(defun policy-check-bloat (action context)
"Warns if a :create-skill action exceeds the bloat threshold.
Size alone is not proof of complexity—a 50KB skill that's well-designed
is better than a 5KB skill that's spaghetti. This check flags for review,
not automatic rejection.
Returns a warning LOG if threshold exceeded, or original action if clean."
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (getf payload :action))
(content (getf payload :content)))
(when (and (eq act :create-skill)
(stringp content)
(> (length content) *policy-max-skill-size-chars*))
(harness-log "POLICY [Bloat]: Proposed skill is ~a chars. Exceeds ~a char threshold."
(length content) *policy-max-skill-size-chars*)
(return-from policy-check-bloat
(list :type :LOG
:payload (list :level :warn
:text (format nil "Bloat Warning: Proposed skill (~a chars) exceeds ~a char threshold. Review for earned complexity."
(length content) *policy-max-skill-size-chars*)
:original-action action))))
action)
#+end_src
** 4. Modularity
*The maxim: "The kernel must survive even if all skills fail."*
Every system should be decomposed into a minimal, unbreakable core and hot-swappable capabilities. Complexity must live at the edges, never in the kernel.
This is the most important invariant for system stability. If the harness grows fat, it becomes:
- Harder to verify for security
- Harder to debug when things go wrong
- Harder to maintain across versions
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defvar *modularity-protected-paths*
'("harness/" "opencortex.asd")
"Paths that constitute the unbreakable core of the system.
Any action targeting these paths must include a :modularity-justification
explaining why the change cannot be implemented as a skill.
The Thin Harness principle: What belongs in the harness?
- Core signal processing (Perceive-Reason-Act loop)
- Memory and persistence primitives
- Protocol definition and validation
- Skills register and dispatch
What belongs in skills?
- Policy and security
- LLM integration
- Domain-specific functionality
- New actuators")
(defun policy-check-modularity (action context)
"Blocks modifications to the system's protected core unless justified.
MODULARITY CHECK:
1. If the action targets a protected path
2. And no :modularity-justification is provided
3. Then block with an explanation
The justification should explain WHY the change cannot be a skill.
Common valid reasons:
- The change fixes a bug in the harness itself
- The change adds a primitive that skills cannot implement
- The change is required for security hardening
Invalid reasons:
- 'It's easier to modify the harness'
- 'Skills are too slow'
- 'I want to keep it all in one place'"
(declare (ignore context))
(let* ((payload (getf action :payload))
(target-file (or (getf payload :file)
(getf payload :filename)))
(justification (or (getf payload :modularity-justification)
(getf payload :MODULARITY-JUSTIFICATION))))
(when (and target-file
(some (lambda (path) (search path target-file))
*modularity-protected-paths*)
(not justification))
(return-from policy-check-modularity
(list :type :LOG
:payload (list :level :error
:text "POLICY [Modularity]: Modification to protected core path blocked. Provide :modularity-justification explaining why this cannot be a skill."
:blocked-path target-file))))
action)
#+end_src
** 5. Technical Mastery & Mentorship
*The maxim: "Teaching is the highest form of assistance."*
The agent's goal is not to "do it for the user," but to "empower the user." Every autonomous action must be explained at a level that increases the user's technical understanding.
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defvar *mentorship-required-actions*
'(:create-skill :eval :modify-file :write-file :replace
:rename-file :delete-file :shell :create-note)
"Actions that trigger the Mentorship invariant.
These are high-impact actions that should come with explanations
not just for the user, but for future debugging and maintenance.")
(defun policy-check-mentorship (action context)
"Blocks high-impact actions that lack a mentorship note.
MENTORSHIP CHECK:
1. If the action is in *mentorship-required-actions*
2. Or if the action calls shell/eval/repair-file tools
3. Then require :mentorship-note explaining what and why
The mentorship note should be:
- Concise (1-2 sentences)
- Educational (explain the principle, not just the action)
- Actionable (help the user understand the outcome)"
(declare (ignore context))
(let* ((payload (getf action :payload))
(act (or (getf payload :action)
(getf action :action)))
(note (or (getf payload :mentorship-note)
(getf payload :MENTORSHIP-NOTE)))
(target (or (getf action :target)
(getf action :TARGET)))
(tool (when (eq target :tool)
(getf payload :tool))))
(when (or (member act *mentorship-required-actions*)
(member tool '("shell" "eval" "repair-file")))
(unless note
(return-from policy-check-mentorship
(list :type :LOG
:payload (list :level :error
:text "POLICY [Mentorship]: High-impact action missing :mentorship-note. Explain what you are doing and why. Blocked.")))))
action)
#+end_src
** 6. Long-Term Sustainability
*The maxim: "Build for the 100-year horizon."*
The Memex should be functional even when:
- Internet is unavailable
- Cloud services are discontinued
- Hardware platforms change
This means preferring local, energy-efficient architectures over cloud-dependent ones.
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defvar *cloud-only-backends* '(:openrouter :openai :anthropic :groq :gemini-api)
"Backends requiring internet connection and external infrastructure.
These are acceptable as fallbacks when local inference is unavailable,
but should be logged as sustainability debt for tracking purposes.")
(defun policy-check-sustainability (action context)
"Logs sustainability debt when action relies on cloud-only infrastructure.
Does NOT block—this is informational, not prohibitive.
Cloud usage is acceptable tactically (when local models fail),
but every cloud usage should be a conscious decision, not a default."
(let* ((payload (getf context :payload))
(backend (getf payload :backend))
(provider (getf payload :provider))
(when (or (member backend *cloud-only-backends*)
(member provider *cloud-only-backends*))
(harness-log "POLICY [Sustainability]: Cloud provider '~a' used. Logged as sustainability debt."
(or backend provider))
(return-from policy-check-sustainability
(list :type :LOG
:payload (list :level :warn
:text (format nil "Sustainability Debt: Reliance on cloud provider '~a'. Consider Ollama or local inference."
(or backend provider))))))
action)
#+end_src
* Policy Explanation Engine
When the policy gate blocks or modifies an action, it must tell the user *why*. This creates an auditable log of every policy decision.
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defun policy-explain (invariant-key message &optional original-action)
"Formats a policy decision into an auditable explanation plist.
INVARIANT-KEY is one of:
:transparency, :autonomy, :bloat, :modularity, :mentorship, :sustainability
MESSAGE is a human-readable string explaining the decision.
ORIGINAL-ACTION is the action that was blocked or modified.
Returns a REQUEST plist addressed to the original source,
containing the explanation and original action for transparency."
(list :type :REQUEST
:target (or (ignore-errors
(getf (getf original-action :meta) :source))
:cli)
:payload (list :action :message
:text (format nil "[POLICY ~a] ~a" invariant-key message)
:explanation (format nil "Invariant: ~a | Rationale: ~a"
invariant-key message)
:original-action original-action)))
#+end_src
* The Policy Gate
The main deterministic entry point for the policy skill. It orchestrates the various invariant checks and delegates to engineering standards.
#+begin_src lisp :tangle ../src/policy.lisp
** Running Invariant Checks
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defun policy-run-invariant-checks (action context)
"Runs all invariant checks in priority order.
Priority order (from *policy-invariant-priorities*):
1. Transparency (500) - blocks non-transparent actions
2. Autonomy (400) - warns on proprietary dependencies
3. Bloat (300) - warns on oversized skills
4. Modularity (250) - blocks unprotected core modifications
5. Mentorship (200) - blocks unexplained high-impact actions
6. Sustainability (100) - warns on cloud dependencies
Returns:
- The final action (possibly modified by checks)
- A blocking LOG event (if any check returned :error level)
- A warning wrapper (if checks returned :warn level but no blocks)"
(let ((checks '(policy-check-transparency
policy-check-autonomy
policy-check-bloat
policy-check-modularity
policy-check-mentorship
policy-check-sustainability)))
(dolist (check-fn checks action)
(let ((result (funcall check-fn action context)))
;; If the check returned a LOG/EVENT, interpret it
(when (and (listp result)
(member (getf result :type) '(:LOG :EVENT)))
(let ((level (getf (getf result :payload) :level)))
(cond
;; Hard block: error level stops processing immediately
((eq level :error)
(return-from policy-run-invariant-checks result))
;; Soft warning: log but continue with original action
(t
(harness-log "~a" (getf (getf result :payload) :text)))))))))
action)
#+end_src
** Finding Engineering Standards
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defun policy-find-engineering-standards-gate ()
"Searches for the Engineering Standards gate across known jailed package names.
The standards skill may be in opencortex-contrib submodule,
so we search multiple possible package names with graceful fallback.
Returns the function symbol, or NIL if unavailable."
(dolist (pkg-name '(:opencortex.skills.org-skill-engineering-standards
:opencortex.skills.org-skill-engineering
:opencortex.skills.engineering-standards)
nil)
(let ((pkg (find-package pkg-name)))
(when pkg
(let ((sym (find-symbol "ENGINEERING-STANDARDS-GATE" pkg)))
(when (and sym (fboundp sym))
(return (symbol-function sym))))))))
#+end_src
** Main Policy Gate
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defun policy-deterministic-gate (action context)
"The main policy gate. Sub-calls engineering standards if available."
(let ((current-action (policy-check-autonomy action context)))
(when current-action
(let ((eng-pkg (find-package :opencortex.skills.org-skill-engineering-standards)))
(when eng-pkg
(let ((eng-gate (find-symbol "ENGINEERING-STANDARDS-GATE" eng-pkg)))
(when (and eng-gate (fboundp eng-gate))
(setf current-action (funcall (symbol-function eng-gate) current-action context)))))))
"The main policy gate entry point.
This function is registered as the deterministic-fn for the policy skill.
It runs invariant checks, then delegates to engineering standards if loaded.
IMPORTANT: Never returns NIL silently. Always returns either:
- An action (possibly modified)
- A blocking LOG event with explanation
- A warning wrapper with explanation"
;; Step 1: Run invariant checks
(let ((current-action (policy-run-invariant-checks action context)))
;; Step 2: If an invariant blocked the action, stop here
(when (and (listp current-action)
(member (getf current-action :type) '(:LOG :EVENT))
(eq (getf (getf current-action :payload) :level) :error))
(return-from policy-deterministic-gate current-action))
;; Step 3: Delegate to Engineering Standards if loaded
(let ((eng-gate (policy-find-engineering-standards-gate)))
(when eng-gate
(setf current-action (funcall eng-gate current-action context))))
current-action))
#+end_src
* Operational Mandates
Every action performed by an agent in this environment must also adhere to the [[file:org-skill-engineering-standards.org][Engineering Standards]].
* Skill Registration
** Skill Registration
#+begin_src lisp :tangle ../src/policy.lisp
#+begin_src lisp :tangle ../library/gen/org-skill-policy.lisp
(defskill :skill-policy
:priority 100
:trigger (lambda (ctx) t)
:priority 500
:trigger (lambda (ctx) (declare (ignore ctx)) t)
:probabilistic nil
:deterministic #'policy-deterministic-gate)
#+end_src
* Quick Reference
** Invariant Quick Reference
| Invariant | Blocks? | Trigger |
|-----------|---------|---------|
| Transparency | Yes | Missing `:explanation` on user-facing actions |
| Autonomy | No | Action references proprietary domain |
| Bloat | No | Skill file exceeds 50KB |
| Modularity | Yes | Modification to `harness/` without justification |
| Mentorship | Yes | High-impact action without `:mentorship-note` |
| Sustainability | No | Action uses cloud-only provider |
** Required Fields by Action Type
| Action | Required Field | Purpose |
|--------|---------------|---------|
| User message | `:explanation` | Transparency |
| Core modification | `:modularity-justification` | Modularity |
| Skill creation | `:mentorship-note` | Mentorship |
| File write | `:mentorship-note` | Mentorship |
* See Also
- [[file:org-skill-engineering-standards.org][Engineering Standards]] (if loaded in opencortex-contrib)
- [[file:../harness/act.org][Act Stage]] - Where Policy and Bouncer gates are invoked
- [[file:../harness/manifest.org][Manifest]] - The Thin Harness philosophy

View File

@@ -45,7 +45,7 @@ Decouple protocol parsing (framing/unframing) from semantic validation.
* Phase D: Build (Implementation)
** Schema Enforcement
#+begin_src lisp :tangle ../src/communication-validator.lisp
#+begin_src lisp :tangle ../library/gen/org-skill-protocol-validator.lisp
(in-package :opencortex)
(defun validate-communication-protocol-schema (msg)
@@ -53,33 +53,38 @@ Decouple protocol parsing (framing/unframing) from semantic validation.
(unless (listp msg)
(error "Communication Protocol Schema Error: Message must be a property list (got ~s)" (type-of msg)))
(let ((type (getf msg :type)))
(unless (member type '(:REQUEST :EVENT :RESPONSE :LOG))
(error "Communication Protocol Schema Error: Invalid message type '~a'" type))
(let ((type (let ((raw (proto-get msg :type))) (if (keywordp raw) (intern (string-upcase (string raw)) :keyword) raw))))
(unless (member type '(:REQUEST :EVENT :RESPONSE :LOG :STATUS :CHAT))
(progn (harness-log "REJECTED MSG: ~s" msg) (error "Communication Protocol Schema Error: Invalid message type '~a'" type)))
(case type
(:REQUEST
(unless (getf msg :target)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :target"))
(unless (getf msg :payload)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :payload")))
;; Allow missing :target if :source is present in :meta, since reason-gate
;; will infer :target from :source downstream. This preserves "equality of
;; clients" — gateways need not duplicate routing logic.
(let ((target (proto-get msg :target))
(source (proto-get (proto-get msg :meta) :source)))
(unless (or target source)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :target and no :source in :meta to infer it"))
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :payload"))))
(:EVENT
(let ((payload (getf msg :payload)))
(let ((payload (proto-get msg :payload)))
(unless (and payload (listp payload))
(error "Communication Protocol Schema Error: EVENT missing or invalid :payload"))
(unless (or (getf payload :action) (getf payload :sensor))
(unless (or (proto-get payload :action) (proto-get payload :sensor))
(error "Communication Protocol Schema Error: EVENT payload must contain :action or :sensor"))))
(:RESPONSE
(unless (getf msg :payload)
(unless (proto-get msg :payload)
(error "Communication Protocol Schema Error: RESPONSE missing mandatory :payload"))))
t))
#+end_src
* Registration
#+begin_src lisp :tangle ../src/communication-validator.lisp
#+begin_src lisp :tangle ../library/gen/org-skill-protocol-validator.lisp
(defskill :skill-communication-protocol-validator
:priority 95
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:protocol-received)))

View File

@@ -41,14 +41,14 @@ The Scribe reacts to the `:heartbeat` sensor. It maintains a state file (`scribe
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(in-package :opencortex)
#+end_src
** State: Checkpoint Management
We track the last processed universal time to avoid redundant distillation.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(defvar *scribe-last-checkpoint* 0
"The universal-time of the last successful distillation run.")
@@ -70,7 +70,7 @@ We track the last processed universal time to avoid redundant distillation.
** Filtering: Privacy & Relevance
The Scribe only cares about non-personal, non-distilled headlines.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(defun scribe-get-distillable-nodes ()
"Returns a list of org-objects from the daily/ folder that require distillation."
(let ((results nil))
@@ -91,7 +91,7 @@ The Scribe only cares about non-personal, non-distilled headlines.
** Probabilistic: Extraction Prompt
The LLM is tasked with identifying atomic concepts within the raw text.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(defun probabilistic-skill-scribe (context)
"Generates the extraction prompt for the Scribe."
(let* ((payload (getf context :payload))
@@ -122,7 +122,7 @@ TEXT:
** Deterministic: Note Committal
The deterministic gate receives the list of proposed notes and writes them to the filesystem.
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(defun scribe-commit-notes (proposals)
"Writes proposed atomic notes to the notes/ directory. Appends if the note exists."
(let ((notes-dir (uiop:merge-pathnames* "notes/" (asdf:system-source-directory :opencortex))))
@@ -144,17 +144,22 @@ The deterministic gate receives the list of proposed notes and writes them to th
(defun verify-skill-scribe (action context)
"Executes the note creation and marks source nodes as distilled."
(declare (ignore context))
(when (and (listp action) (not (member (getf action :type) '(:LOG :EVENT))))
;; Action is the list of note plists from the LLM
(scribe-commit-notes action)
(scribe-save-state)
(harness-log "SCRIBE: Distillation complete.")
;; Return a log event to stop the loop
(list :type :LOG :payload (list :text "Distillation successful."))))
(let ((data (cond ((and (listp action) (eq (getf action :type) :REQUEST))
(getf (getf action :payload) :payload))
((and (listp action) (not (member (getf action :type) '(:LOG :EVENT))))
action)
(t nil))))
(when data
(harness-log "SCRIBE: Committing ~a atomic notes..." (length data))
(scribe-commit-notes data)
(scribe-save-state)
(harness-log "SCRIBE: Distillation complete.")
;; Return a log event to stop the loop
(list :type :LOG :payload (list :text "Distillation successful.")))))
#+end_src
** Skill Registration
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(defskill :skill-scribe
:priority 50
:trigger (lambda (ctx)
@@ -169,6 +174,6 @@ The deterministic gate receives the list of proposed notes and writes them to th
#+end_src
** Initialization
#+begin_src lisp
#+begin_src lisp :tangle ../library/gen/org-skill-scribe.lisp
(scribe-load-state)
#+end_src

View File

@@ -1,234 +1,69 @@
:PROPERTIES:
:ID: 0ae190ec-5658-4630-aed8-a5e9ffbbea0e
:CREATED: [2026-03-30 Mon 21:16]
:EDITED: [2026-04-07 Tue 13:42]
:ID: shell-actuator-skill
:CREATED: [2026-04-12 Sun]
:END:
#+TITLE: SKILL: Shell Actuator Agent (Universal Literate Note)
#+TITLE: SKILL: Shell Actuator (Secure Host Interaction)
#+STARTUP: content
#+FILETAGS: :shell:actuator:system:autonomy:
#+FILETAGS: :actuator:shell:system:autonomy:
* Overview
The *Shell Actuator Agent* provides the bridge to the host operating system. It enables secure command execution while maintaining a strict security posture through whitelisting and diagnostic feedback loops.
The *Shell Actuator* provides a controlled interface for the OpenCortex to execute commands on the host operating system.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: FROZEN
:END:
* Implementation
** 1. Purpose
Define a secure, diagnostic-rich interface for host OS interaction.
#+begin_src lisp :tangle ../library/gen/org-skill-shell-actuator.lisp
** 2. User Needs
- *Secure Actuation:* Strict whitelist of permitted commands.
- *Diagnostic Feedback:* Capture STDOUT, STDERR, and exit codes.
- *Loop Closure:* Automatic neural analysis of command results.
- *Resilience:* Graceful handling of blocked or failed commands.
** 3. Success Criteria
*** TODO Whitelist Enforcement
*** TODO Diagnostic Capture
*** TODO Result Analysis Loop
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
Interfaces for secure system calls. State is event-driven via the core kernel bus.
** 2. Semantic Interfaces
#+begin_src lisp
(defun execute-shell-safely (action)
"Verifies command against whitelist and captures diagnostics.")
(defun trigger-skill-shell-actuator (context)
"Monitors for shell-response events.")
** 3. Success Criteria
*** DONE Whitelist Enforcement
- Verified that only `*allowed-commands*` can be executed.
- Added a strict `*shell-metacharacters*` check to block command injection.
*** DONE Diagnostic Capture
- Verified that STDOUT, STDERR, and Exit Codes are correctly captured and re-injected.
*** DONE Result Analysis Loop
- The `:probabilistic` component successfully formats command results for Autonomous review.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
Interfaces for secure system calls. State is event-driven via the core kernel bus.
** 2. Semantic Interfaces
#+begin_src lisp
(defun execute-shell-safely (action)
"Verifies command against whitelist and metacharacter blacklist, then captures diagnostics.")
(defun trigger-skill-shell-actuator (context)
"Monitors for shell-response events.")
(defun probabilistic-skill-shell-actuator (context)
"Neural interpretation of command diagnostics.")
#+end_src
* Phase D: Build (Implementation)
** Allowed Commands
Whitelist of permitted host binaries.
#+begin_src lisp
(defparameter *allowed-commands* '("ls" "git" "rg" "grep" "date" "echo" "cat" "node" "python3" "sbcl"))
#+end_src
** Shell Metacharacters
Dangerous characters that are banned to prevent command injection.
(defparameter *shell-metacharacters* '(#\; #\& #\| #\> #\< #\$ #\` #\\ #\!))
#+begin_src lisp
(defparameter *shell-metacharacters* '(#\; #\& #\| #\> #\< #\$ #\` #\\ #\!)
"Characters that are banned in shell commands to prevent injection.")
#+end_src
** Safety Check (shell-command-safe-p)
Predicate to verify a command string is free of metacharacters.
#+begin_src lisp
(defun shell-command-safe-p (cmd-string)
"Returns T if the command string contains no dangerous metacharacters."
(not (some (lambda (char) (find char cmd-string)) *shell-metacharacters*)))
#+end_src
** Shell Execution (execute-shell-safely)
The primary secure actuator for host system calls.
#+begin_src lisp
(defun execute-shell-safely (action context)
(let* ((cmd-string (getf (getf action :payload) :cmd))
(let* ((payload (getf action :PAYLOAD))
(cmd-string (getf payload :cmd))
(executable (car (uiop:split-string (string-trim " " cmd-string) :separator '(#\Space)))))
(cond
;; 1. Metacharacter check (Injection prevention)
((not (shell-command-safe-p cmd-string))
(opencortex:inject-stimulus
`(:type :EVENT :payload (:sensor :shell-response :cmd ,cmd-string :stdout "" :stderr "ERROR - Security Violation: Dangerous metacharacters detected." :exit-code 1))
`(:TYPE :EVENT :PAYLOAD (:SENSOR :shell-response :cmd ,cmd-string :stdout "" :stderr "ERROR - Security Violation: Dangerous metacharacters detected." :exit-code 1))
:stream (getf context :reply-stream)))
;; 2. Whitelist check
((not (member executable *allowed-commands* :test #'string=))
(opencortex:inject-stimulus
`(:type :EVENT :payload (:sensor :shell-response :cmd ,cmd-string :stdout "" :stderr "ERROR - Command not in security whitelist." :exit-code 1))
`(:TYPE :EVENT :PAYLOAD (:SENSOR :shell-response :cmd ,cmd-string :stdout "" :stderr "ERROR - Command not in security whitelist." :exit-code 1))
:stream (getf context :reply-stream)))
;; 3. Safe Execution
(t
(multiple-value-bind (stdout stderr exit-code)
(uiop:run-program cmd-string :output :string :error-output :string :ignore-error-status t)
(opencortex:inject-stimulus
`(:type :EVENT :payload (:sensor :shell-response :cmd ,cmd-string :stdout ,(or stdout "") :stderr ,(or stderr "") :exit-code ,exit-code))
`(:TYPE :EVENT :PAYLOAD (:SENSOR :shell-response :cmd ,cmd-string :stdout ,(or stdout "") :stderr ,(or stderr "") :exit-code ,exit-code))
:stream (getf context :reply-stream)))))))
#+end_src
** Script Synthesis (execute-sandboxed-script)
Executes a synthesized script (Python/Lisp/JS) in a controlled directory.
#+begin_src lisp
(defun execute-sandboxed-script (action context)
"Executes a synthesized script (Python/Lisp/JS) in a controlled directory.
This enables SOTA-level Tool Synthesis and Iterative Fixing."
(let* ((payload (getf action :payload))
(language (getf payload :language))
(content (getf payload :content))
(sandbox-dir "/tmp/opencortex-sandbox/")
(filename (format nil "synth-~a.~a" (get-universal-time) (case language (:python "py") (:lisp "lisp") (:js "js") (t "txt"))))
(full-path (format nil "~a~a" sandbox-dir filename)))
(ensure-directories-exist sandbox-dir)
(with-open-file (out full-path :direction :output :if-exists :supersede)
(write-string content out))
(let ((cmd (case language
(:python (format nil "python3 ~a" full-path))
(:lisp (format nil "sbcl --script ~a" full-path))
(:js (format nil "node ~a" full-path)))))
(multiple-value-bind (stdout stderr exit-code)
(uiop:run-program cmd :output :string :error-output :string :ignore-error-status t)
(opencortex:inject-stimulus
`(:type :EVENT :payload (:sensor :shell-response :cmd ,cmd :stdout ,(or stdout "") :stderr ,(or stderr "") :exit-code ,exit-code :synthesis-p t))
:stream (getf context :reply-stream))))))
#+end_src
** Infrastructure: MicroVM Provisioning
Hardware-Level Isolation for future security evolution.
#+begin_src lisp
(defun provision-microvm (id &key (cpu 1) (ram 512))
"Hardware-Level Isolation: Provisions an ephemeral Firecracker MicroVM.
This is the high-security evolution of directory-based sandboxing."
(harness-log "SECURITY [Hardware] - Provisioning MicroVM ~a (CPU: ~a, RAM: ~aMB)..." id cpu ram)
;; Future implementation: Wraps 'fcvm' or 'firecracker' CLI calls.
(format nil "vm-~a-provisioned" id))
#+end_src
** Feedback Perception
#+begin_src lisp
(defun trigger-skill-shell-actuator (context)
(let ((type (getf context :type))
(payload (getf context :payload)))
(let ((type (getf context :TYPE))
(payload (getf context :PAYLOAD)))
(and (eq type :EVENT)
(eq (getf payload :sensor) :shell-response))))
#+end_src
(eq (getf payload :SENSOR) :shell-response))))
** Probabilistic-Cognitive Analysis
#+begin_src lisp
(defun probabilistic-skill-shell-actuator (context)
(let* ((p (getf context :payload))
(let* ((p (getf context :PAYLOAD))
(cmd (getf p :cmd))
(stdout (getf p :stdout))
(stderr (getf p :stderr))
(exit-code (getf p :exit-code))
(synthesis-p (getf p :synthesis-p)))
(if synthesis-p
(format nil "
TOOL SYNTHESIS RESULT:
Command: ~a (Exit: ~a)
STDOUT: ~a
STDERR: ~a
TASK:
If the command failed (Exit != 0), analyze the STDERR and propose a FIX for the script.
If it succeeded, use the STDOUT to complete the original goal.
" cmd exit-code stdout stderr)
(let ((result-text (format nil "* Shell Command Result
- Command: ~a
- Exit Code: ~a
(exit-code (getf p :exit-code)))
(format nil "SHELL COMMAND RESULT:
Command: ~a
Exit Code: ~a
STDOUT: ~a
STDERR: ~a" cmd exit-code stdout stderr)))
** STDOUT
#+begin_example
~a
#+end_example
** STDERR
#+begin_example
~a
#+end_example"
cmd exit-code stdout stderr)))
`(:type :request :target :emacs :payload (:action :insert-at-end :buffer "*opencortex-chat*" :text ,result-text))))))
#+end_src
* Registration
** Registration: Actuator
Register the shell channel as a physical actuator.
#+begin_src lisp
(opencortex:register-actuator :shell #'execute-shell-safely)
#+end_src
** Registration: Skill
Define the skill entry for the shell actuator.
#+begin_src lisp
(defskill :skill-shell-actuator
:priority 80
:trigger #'trigger-skill-shell-actuator

View File

@@ -1,101 +0,0 @@
(in-package :opencortex)
(defvar *default-actuator* :cli)
(defvar *silent-actuators* '(:cli :system-message :emacs))
(defun initialize-actuators ()
"Loads actuator routing defaults from environment variables and registers core harness actuators."
(let ((def (uiop:getenv "DEFAULT_ACTUATOR"))
(silent (uiop:getenv "SILENT_ACTUATORS")))
(when def
(setf *default-actuator* (intern (string-upcase def) "KEYWORD")))
(when silent
(setf *silent-actuators*
(mapcar (lambda (s) (intern (string-upcase (string-trim '(#\Space) s)) "KEYWORD"))
(str:split "," silent)))))
;; Register core harness actuators
(register-actuator :system #'execute-system-action)
(register-actuator :tool #'execute-tool-action))
(defun dispatch-action (action context)
"Routes an approved action to its registered physical actuator."
(when (and action (listp action))
(let* ((target (or (ignore-errors (getf action :target)) *default-actuator*))
(actuator-fn (gethash target *actuator-registry*)))
(if actuator-fn
(funcall actuator-fn action context)
(harness-log "ACT ERROR: No actuator for ~a" target)))))
(defun execute-system-action (action context)
"Processes internal harness commands. (ACTUATOR)"
(declare (ignore context))
(let* ((payload (ignore-errors (getf action :payload)))
(cmd (ignore-errors (getf payload :action))))
(case cmd
(:eval (let ((code (getf payload :code)))
(eval (read-from-string code))))
(:create-skill (let* ((filename (getf payload :filename)) (content (getf payload :content))
(skills-dir (merge-pathnames "skills/" (asdf:system-source-directory :opencortex)))
(full-path (merge-pathnames filename skills-dir)))
(with-open-file (out full-path :direction :output :if-exists :supersede) (write-string content out))
(load-skill-from-org full-path)))
(:message (harness-log "ACT [System]: ~a" (getf payload :text)))
(t (harness-log "ACT ERROR [System]: Unknown command ~s" cmd)))))
(defun execute-tool-action (action context)
"Executes a registered cognitive tool. (ACTUATOR)"
(let* ((payload (getf action :payload))
(tool-name (getf payload :tool))
(tool-args (getf payload :args))
(depth (getf context :depth 0))
(tool (gethash (string-downcase (string tool-name)) *cognitive-tools*)))
(if tool
(handler-case
(let* ((clean-args (if (and (listp tool-args) (listp (car tool-args))) (car tool-args) tool-args))
(result (funcall (cognitive-tool-body tool) clean-args)))
(list :type :EVENT :depth (1+ depth) :reply-stream (getf context :reply-stream)
:payload (list :sensor :tool-output :result result :tool tool-name)))
(error (c)
(list :type :EVENT :depth (1+ depth) :reply-stream (getf context :reply-stream)
:payload (list :sensor :tool-error :tool tool-name :message (format nil "~a" c)))))
(list :type :EVENT :depth (1+ depth) :reply-stream (getf context :reply-stream)
:payload (list :sensor :tool-error :message "Tool not found")))))
(defun act-gate (signal)
"Final Stage: Actuation and feedback generation."
(let* ((approved (getf signal :approved-action))
(type (getf signal :type))
(feedback nil))
;; 1. Last-Mile Safety Check (The Bouncer & Deterministic Gates)
(when approved
(let ((verified (deterministic-verify approved signal)))
(if (and (listp verified) (member (getf verified :type) '(:LOG :EVENT :log :event)))
(progn
(harness-log "ACT BLOCKED: Action failed last-mile deterministic check.")
(setf (getf signal :approved-action) nil)
(setf approved nil)
(setf feedback verified))
(progn
(setf (getf signal :approved-action) verified)
(setf approved verified)))))
;; 2. Actuation Logic
(case type
(:REQUEST (dispatch-action signal signal))
(:EVENT
(when approved
(let* ((target (getf approved :target))
(result (dispatch-action approved signal)))
;; If the actuator returns a signal (like :tool-output), it becomes the feedback.
;; Otherwise, generate tool-output feedback for non-silent actuators.
(cond ((and (listp result) (member (getf result :type) '(:EVENT :LOG)))
(setf feedback result))
((and result (not (member target *silent-actuators*)))
(setf feedback (list :type :EVENT :depth (1+ (getf signal :depth 0))
:reply-stream (getf signal :reply-stream)
:payload (list :sensor :tool-output :result result :tool approved)))))))))
(setf (getf signal :status) :acted)
feedback))

View File

@@ -1,39 +0,0 @@
(in-package :opencortex)
(defun validate-communication-protocol-schema (msg)
"Strict structural validation for incoming communication protocol messages."
(unless (listp msg)
(error "Communication Protocol Schema Error: Message must be a property list (got ~s)" (type-of msg)))
(let ((type (getf msg :type)))
(unless (member type '(:REQUEST :EVENT :RESPONSE :LOG))
(error "Communication Protocol Schema Error: Invalid message type '~a'" type))
(case type
(:REQUEST
(unless (getf msg :target)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :target"))
(unless (getf msg :payload)
(error "Communication Protocol Schema Error: REQUEST missing mandatory :payload")))
(:EVENT
(let ((payload (getf msg :payload)))
(unless (and payload (listp payload))
(error "Communication Protocol Schema Error: EVENT missing or invalid :payload"))
(unless (or (getf payload :action) (getf payload :sensor))
(error "Communication Protocol Schema Error: EVENT payload must contain :action or :sensor"))))
(:RESPONSE
(unless (getf msg :payload)
(error "Communication Protocol Schema Error: RESPONSE missing mandatory :payload"))))
t))
(defskill :skill-communication-protocol-validator
:priority 95
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:protocol-received)))
:probabilistic nil
:deterministic (lambda (action ctx)
(declare (ignore ctx))
(validate-communication-protocol-schema action)
action))

View File

@@ -1,67 +0,0 @@
(in-package :opencortex)
(defvar *actuator-registry* (make-hash-table :test 'equal)
"Global registry mapping target keywords to their physical actuator functions.")
(defun register-actuator (name fn)
"Registers an actuator function. Actuators receive: (ACTION CONTEXT)."
(setf (gethash name *actuator-registry*) fn))
(defun frame-message (msg-string)
"Prefixes MSG-STRING with a 6-character hex length.
If security is enabled, prefixes a 64-char HMAC-SHA256 signature."
(let ((len (length msg-string))
(enforce-hmac (uiop:getenv "COMMUNICATION_PROTOCOL_ENFORCE_HMAC")))
(if (and enforce-hmac (string-equal enforce-hmac "true"))
(let ((secret (uiop:getenv "COMMUNICATION_PROTOCOL_HMAC_SECRET")))
(unless secret (error "COMMUNICATION_PROTOCOL_HMAC_SECRET is required when security is enabled."))
(let* ((key (ironclad:ascii-string-to-byte-array secret))
(hmac (ironclad:make-mac :hmac key :sha256))
(payload-bytes (ironclad:ascii-string-to-byte-array msg-string)))
(ironclad:update-mac hmac payload-bytes)
(let ((signature (ironclad:byte-array-to-hex-string (ironclad:produce-mac hmac))))
(format nil "~(~6,'0x~)~a~a" len signature msg-string))))
(format nil "~(~6,'0x~)~a" len msg-string))))
(defun parse-message (framed-string)
"Extracts and parses the S-expression from a framed string securely."
(when (< (length framed-string) 6)
(error "Framed string too short"))
(let* ((enforce-hmac (uiop:getenv "COMMUNICATION_PROTOCOL_ENFORCE_HMAC"))
(use-hmac (and enforce-hmac (string-equal enforce-hmac "true")))
(prefix-len (if use-hmac 70 6)))
(when (< (length framed-string) prefix-len)
(error "Framed string too short for communication protocol prefix"))
(let* ((len-str (subseq framed-string 0 6))
(signature (when use-hmac (subseq framed-string 6 70)))
(actual-msg (subseq framed-string prefix-len))
(expected-len (ignore-errors (parse-integer len-str :radix 16))))
(unless expected-len
(error "Invalid hex length prefix: ~a" len-str))
(unless (= expected-len (length actual-msg))
(error "Message length mismatch. Expected ~a, got ~a" expected-len (length actual-msg)))
(when use-hmac
(let ((secret (uiop:getenv "COMMUNICATION_PROTOCOL_HMAC_SECRET")))
(unless secret (error "COMMUNICATION_PROTOCOL_HMAC_SECRET is required when security is enabled."))
(let* ((key (ironclad:ascii-string-to-byte-array secret))
(hmac (ironclad:make-mac :hmac key :sha256))
(payload-bytes (ironclad:ascii-string-to-byte-array actual-msg)))
(ironclad:update-mac hmac payload-bytes)
(let ((expected-signature (ironclad:byte-array-to-hex-string (ironclad:produce-mac hmac))))
(unless (string-equal signature expected-signature)
(error "communication protocol Integrity Failure: HMAC mismatch"))))))
;; SECURITY: Disable the reader's ability to execute code during parsing
(let ((*read-eval* nil))
(let ((msg (read-from-string actual-msg)))
(validate-communication-protocol-schema msg)
msg)))))
(defun make-hello-message (version)
"Constructs the standard HELLO handshake message."
(list :type :EVENT
:payload (list :action :handshake
:version version
:capabilities '(:auth :swank :org-ast))))

View File

@@ -1,422 +0,0 @@
;;; opencortex.el --- Probabilistic-Deterministic Lisp Machine Kernel for Org-mode -*- lexical-binding: t; -*-
;; Copyright (C) 2026 Amr
;;
;; Author: Amr
;; Version: 0.1.0
;; Package-Requires: ((emacs "27.1"))
;; Keywords: convenience, org
;; URL: https://github.com/amr/opencortex
;;; Commentary:
;; opencortex provides a Probabilistic-Deterministic Lisp Machine interface for Emacs.
;; It acts as the sensor/actuator array, communicating with a persistent
;; Common Lisp daemon over a high-speed communication protocol socket.
;;; Code:
(require 'json)
(require 'cl-lib)
(require 'org-id)
(require 'org-element)
(defgroup opencortex nil
"Emacs interface for the opencortex Common Lisp daemon."
:group 'org)
(defcustom opencortex-port 9105
"The port the opencortex daemon is listening on."
:type 'integer
:group 'opencortex)
(defcustom opencortex-host "127.0.0.1"
"The host the opencortex daemon is running on."
:type 'string
:group 'opencortex)
(defcustom opencortex-executable-path "opencortex-server"
"Path to the compiled opencortex-server binary.
If nil, Emacs will not attempt to start the daemon automatically and
will assume you have started it manually (e.g., via SBCL)."
:type '(choice (string :tag "Path to executable")
(const :tag "Manual daemon management" nil))
:group 'opencortex)
(defvar opencortex--network-process nil
"The network process connected to the daemon.")
(defvar opencortex--daemon-process nil
"The spawned daemon child process.")
(defun opencortex--start-daemon ()
"Start the daemon binary if not already running."
(when (and opencortex-executable-path
(not (process-live-p opencortex--daemon-process)))
(message "opencortex: Starting daemon (%s)..." opencortex-executable-path)
(setq opencortex--daemon-process
(make-process
:name "opencortex-daemon"
:buffer "*opencortex-daemon*"
:command (list opencortex-executable-path (number-to-string opencortex-port))
:connection-type 'pipe))
;; Give it a moment to bind to the port
(sleep-for 1.0)))
(defun opencortex-connect ()
"Connect to the opencortex daemon, starting it if necessary."
(interactive)
(when opencortex--network-process
(delete-process opencortex--network-process))
(opencortex--start-daemon)
(condition-case err
(progn
(setq opencortex--network-process
(make-network-process
:name "opencortex"
:buffer "*opencortex*"
:family 'ipv4
:host opencortex-host
:service opencortex-port
:filter #'opencortex--filter
:sentinel #'opencortex--sentinel))
(message "opencortex: Connected to daemon."))
(error
(message "opencortex: Failed to connect to daemon at %s:%s. Ensure it is running. Error: %s"
opencortex-host opencortex-port (error-message-string err)))))
(defun opencortex-disconnect ()
"Disconnect from the opencortex daemon."
(interactive)
(when opencortex--network-process
(delete-process opencortex--network-process)
(setq opencortex--network-process nil)
(message "opencortex: Disconnected from network."))
(when opencortex--daemon-process
(delete-process opencortex--daemon-process)
(setq opencortex--daemon-process nil)
(message "opencortex: Killed daemon process.")))
(defun opencortex--filter (proc string)
"Handle incoming communication protocol messages from the daemon via PROC with STRING."
(let ((buf (process-buffer proc)))
(when (buffer-live-p buf)
(with-current-buffer buf
(goto-char (point-max))
(insert string)
(opencortex--process-buffer buf proc)))))
(defun opencortex--process-buffer (buffer &optional proc)
"Process the communication protocol message BUFFER, optionally using PROC."
(with-current-buffer buffer
(goto-char (point-min))
(while (>= (buffer-size) 6)
(let* ((len-str (buffer-substring (point-min) (+ (point-min) 6)))
(msg-len (string-to-number len-str 16)))
(if (>= (buffer-size) (+ 6 msg-len))
(let* ((msg-start (+ (point-min) 6))
(msg-end (+ msg-start msg-len))
(msg-str (buffer-substring msg-start msg-end))
(plist (car (read-from-string msg-str))))
(delete-region (point-min) msg-end)
(opencortex--handle-message proc plist))
;; Message incomplete, stop loop
(goto-char (point-max))
(setq msg-len 1000000)))))) ; Break loop
(defun opencortex--plist-get (plist prop)
"Case-insensitive keyword lookup for communication protocol compatibility."
(or (plist-get plist prop)
(plist-get plist (intern (upcase (symbol-name prop))))
(plist-get plist (intern (downcase (symbol-name prop))))))
(defun opencortex--handle-message (proc plist)
"Route and execute incoming communication protocol messages from PROC using PLIST."
(let ((type (opencortex--plist-get plist :type))
(id (opencortex--plist-get plist :id))
(payload (or (opencortex--plist-get plist :payload) plist)))
(cond
((member type '(:request :REQUEST))
(opencortex--execute-request proc id payload))
((member type '(:response :RESPONSE))
(message "opencortex: Received response for ID %s" id))
((member type '(:log :LOG))
(let ((text (opencortex--plist-get payload :text)))
(opencortex--insert-to-history (concat "[reasoning] " text "\n") 'opencortex-system-face)))
(t (message "opencortex: Received unknown message type %s" type)))))
(defun opencortex--execute-request (proc id payload)
"Execute an actuator request from the daemon via PROC with ID and PAYLOAD."
(let ((action (opencortex--plist-get payload :action)))
(cond
((member action '(:eval :EVAL))
(let ((code (opencortex--plist-get payload :code)))
(condition-case err
(let ((result (eval (read code))))
(opencortex-send
`(:type :RESPONSE :id ,id :payload (:status :success :result ,(format "%s" result)))))
(error
(opencortex-send
`(:type :RESPONSE :id ,id :payload (:status :error :message ,(error-message-string err))))))))
((member action '(:message :MESSAGE))
(message "opencortex [DAEMON]: %s" (opencortex--plist-get payload :text))
(opencortex-send `(:type :RESPONSE :id ,id :payload (:status :success))))
((member action '(:insert-at-end :INSERT-AT-END))
(let ((text (opencortex--plist-get payload :text)))
(opencortex--insert-to-history (concat "\nAGENT: " text "\n\n"))
(opencortex-send `(:type :RESPONSE :id ,id :payload (:status :success)))))
((member action '(:refactor-subtree :REFACTOR-SUBTREE))
(let ((target-id (opencortex--plist-get payload :target-id))
(properties (opencortex--plist-get payload :properties)))
(condition-case err
(save-excursion
(when target-id (org-id-goto target-id))
(dolist (prop properties)
(org-set-property (car prop) (cdr prop)))
(opencortex-send `(:type :RESPONSE :id ,id :payload (:status :success))))
(error
(opencortex-send
`(:type :RESPONSE :id ,id :payload (:status :error :message ,(error-message-string err))))))))
(t
(message "opencortex: Unknown action %s" action)
(opencortex-send `(:type :RESPONSE :id ,id :payload (:status :unsupported)))))))
(defun opencortex--sentinel (proc event)
"Handle network process PROC lifecycle EVENT."
(when (string-match "finished" event)
(setq opencortex--network-process nil)
(message "opencortex: Connection lost.")))
(defun opencortex-send (plist)
"Send a Lisp PLIST to the daemon using communication protocol framing."
(let* ((msg (prin1-to-string plist))
(len (length msg))
(framed (format "%06x%s" len msg)))
(if (and opencortex--network-process (process-live-p opencortex--network-process))
(process-send-string opencortex--network-process framed)
(message "opencortex (offline): %s" framed))))
(defun opencortex--buffer-to-sexp ()
"Transform the current Org buffer into a pure Lisp AST (plist)."
(opencortex--clean-element (org-element-parse-buffer)))
(defun opencortex--clean-element (element)
"Recursively transform an Org ELEMENT into a pure Lisp plist."
(cond
((listp element)
(let* ((type (car element))
(props (nth 1 element))
(children (nthcdr 2 element))
(cleaned-props nil))
;; Filter and transform properties
(cl-loop for (key val) on props by 'cddr do
(unless (member key '(:standard-properties :parent :buffer))
(let ((json-val (cond
((stringp val) val)
((numberp val) val)
((booleanp val) val)
(t (format "%s" val)))))
(setq cleaned-props (plist-put cleaned-props key json-val)))))
;; Explicitly capture TODO state
(let ((todo (org-element-property :todo-keyword element)))
(when todo
(setq cleaned-props (plist-put cleaned-props :TODO-STATE (format "%s" todo)))))
(list :type type
:properties cleaned-props
:contents (mapcar #'opencortex--clean-element children))))
((stringp element) element)
(t (format "%s" element))))
;;; Sensors
(defun opencortex-notify-save ()
"Sensor: Notify daemon with full Semantic Perception (AST) when saved."
(when (and opencortex--network-process (derived-mode-p 'org-mode))
(opencortex-send
`(:type :EVENT
:payload (:sensor :buffer-update
:file ,(buffer-file-name)
:state :saved
:ast ,(opencortex--buffer-to-sexp))))))
(defun opencortex-notify-point ()
"Sensor: Notify daemon of the element currently at point (Incremental Perception).
This is much faster than parsing the entire buffer and allows for real-time
responsiveness to the user's cursor position."
(when (and opencortex--network-process (derived-mode-p 'org-mode))
(let ((element (org-element-at-point)))
(opencortex-send
`(:type :EVENT
:payload (:sensor :point-update
:file ,(buffer-file-name)
:element ,(opencortex--clean-element element)))))))
;;; Interaction Commands
(defun opencortex-set-model-cascade (cascade-string)
"Set the ordered list of LLM providers to use as fallbacks.
CASCADE-STRING should be a comma-separated list of keywords,
e.g., ':gemini,:openai,:ollama'."
(interactive "sEnter model cascade (e.g. :gemini,:openai): ")
(unless opencortex--network-process
(opencortex-connect))
(let ((cascade (mapcar #'intern (split-string cascade-string ","))))
(opencortex-send
`(:type :REQUEST
:id ,(truncate (float-time))
:target :system
:payload (:action :set-cascade :cascade ,cascade)))
(message "opencortex: Requesting model cascade update to %s" cascade)))
(defgroup opencortex-faces nil
"Faces for the opencortex chat interface."
:group 'opencortex)
(defface opencortex-user-face
'((((class color) (background dark)) :foreground "LightSkyBlue" :weight bold)
(((class color) (background light)) :foreground "blue" :weight bold)
(t :weight bold :underline t))
"Face for user messages in chat history."
:group 'opencortex-faces)
(defface opencortex-system-face
'((t :slant italic :foreground "gray50"))
"Face for system and reasoning logs."
:group 'opencortex-faces)
(defun opencortex-chat ()
"Modern chat interface for the opencortex kernel.
Opens a history buffer and a dedicated input area."
(interactive)
(let ((chat-buf (get-buffer-create "*opencortex-chat*"))
(input-buf (get-buffer-create "*opencortex-input*")))
;; History Buffer Setup
(with-current-buffer chat-buf
(unless (eq major-mode 'special-mode)
(special-mode)
(let ((inhibit-read-only t))
(erase-buffer)
(insert "--- opencortex History ---\n\n"))))
;; Input Buffer Setup
(with-current-buffer input-buf
(unless (eq major-mode 'org-mode)
(org-mode)
(local-set-key (kbd "C-c C-c") #'opencortex-chat-send)
(local-set-key (kbd "C-c C-k") #'opencortex-interrupt))
(let ((inhibit-read-only t))
(delete-region (point-min) (point-max))
(insert "# Type your message and press C-c C-c to send.\n")))
;; Layout: Chat History (Top), Input Area (Bottom)
(delete-other-windows)
(switch-to-buffer chat-buf)
(let ((win (split-window-below -6))) ; 6 lines for input
(set-window-buffer win input-buf)
(select-window win))))
(defun opencortex-interrupt ()
"Interrupt the opencortex reasoning loop."
(interactive)
(unless opencortex--network-process
(opencortex-connect))
(opencortex-send
`(:type :EVENT
:payload (:sensor :interrupt)))
(message "opencortex: Interrupt signal sent."))
(defun opencortex--insert-to-history (text &optional face)
"Insert TEXT into the chat history buffer with optional FACE and scroll."
(let ((buf (get-buffer-create "*opencortex-chat*")))
(with-current-buffer buf
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-max))
(insert (if face (propertize text 'face face) text)))
;; Force scroll in all windows showing this buffer
(walk-windows
(lambda (w)
(when (eq (window-buffer w) buf)
(set-window-point w (point-max))))
nil t)))))
(defun opencortex-chat-send ()
"Send the current chat buffer content to the agent."
(interactive)
(unless opencortex--network-process
(opencortex-connect))
(let* ((text (buffer-substring-no-properties (point-min) (point-max)))
(clean-text (string-trim (replace-regexp-in-string "^#.*\n" "" text))))
(when (> (length clean-text) 0)
;; Append to history with styling
(opencortex--insert-to-history (concat "YOU: " clean-text "\n\n") 'opencortex-user-face)
;; Clear input buffer
(let ((inhibit-read-only t))
(delete-region (point-min) (point-max))
(insert "# Type your message and press C-c C-c to send.\n"))
;; Send to daemon
(opencortex-send
`(:type :EVENT
:payload (:sensor :chat-message
:text ,clean-text)))
(message "opencortex: Message sent."))))
(defun opencortex-auth-google (code)
"Submit the Google OAuth authorization CODE to the daemon."
(interactive "sEnter Google Authorization Code: ")
(unless opencortex--network-process
(opencortex-connect))
(opencortex-send
`(:type :REQUEST
:id ,(truncate (float-time))
:target :system
:payload (:action :auth-google-code :code ,code)))
(message "opencortex: Authorization code sent to daemon."))
(defun opencortex-organize-subtree ()
...
"Command: Ask the agent to organize the current Org subtree."
(interactive)
(opencortex-run-command :organize-subtree))
(defun opencortex-summarize-buffer ()
"Command: Ask the agent to summarize the current buffer."
(interactive)
(opencortex-run-command :summarize-buffer))
(defun opencortex-run-command (command-type)
"Generic runner for high-level COMMAND-TYPE."
(unless opencortex--network-process
(opencortex-connect))
(let ((ast (opencortex--buffer-to-sexp)))
(opencortex-send
`(:type :EVENT
:payload (:sensor :user-command
:command ,command-type
:file ,(buffer-file-name)
:ast ,ast)))
(message "opencortex: Requesting '%s'..." command-type)))
;;;###autoload
(define-minor-mode opencortex-mode
"Global minor mode for the opencortex Probabilistic-Deterministic kernel.
When enabled, this mode starts the Lisp daemon (if configured)
and establishes the network connection to enable proactive
Org-mode sensing."
:global t
:group 'opencortex
(if opencortex-mode
(progn
(add-hook 'after-save-hook #'opencortex-notify-save)
(add-hook 'post-command-hook #'opencortex-notify-point)
(add-hook 'kill-emacs-hook #'opencortex-disconnect)
(opencortex-connect))
(remove-hook 'after-save-hook #'opencortex-notify-save)
(remove-hook 'post-command-hook #'opencortex-notify-point)
(remove-hook 'kill-emacs-hook #'opencortex-disconnect)
(opencortex-disconnect)))
(provide 'opencortex)
;;; opencortex.el ends here

View File

@@ -1,25 +0,0 @@
(in-package :opencortex)
(defun policy-check-autonomy (action context)
"Ensures the action does not violate the Autonomy invariant."
(declare (ignore context))
;; Implementation placeholder: currently permits all actions.
;; Future: Scan for non-autonomous domain names or proprietary API endpoints.
action)
(defun policy-deterministic-gate (action context)
"The main policy gate. Sub-calls engineering standards if available."
(let ((current-action (policy-check-autonomy action context)))
(when current-action
(let ((eng-pkg (find-package :opencortex.skills.org-skill-engineering-standards)))
(when eng-pkg
(let ((eng-gate (find-symbol "ENGINEERING-STANDARDS-GATE" eng-pkg)))
(when (and eng-gate (fboundp eng-gate))
(setf current-action (funcall (symbol-function eng-gate) current-action context)))))))
current-action))
(defskill :skill-policy
:priority 100
:trigger (lambda (ctx) t)
:probabilistic nil
:deterministic #'policy-deterministic-gate)

View File

@@ -1,123 +0,0 @@
(in-package :opencortex)
(defvar *probabilistic-backends* (make-hash-table :test 'equal))
(defvar *provider-cascade* nil)
(defun register-probabilistic-backend (name fn) (setf (gethash name *probabilistic-backends*) fn))
(defvar *model-selector-fn* nil "A function called with (provider context) to return a model ID.")
(defvar *consensus-enabled-p* nil "If T, ask-probabilistic queries all backends in parallel.")
(defun ask-probabilistic (prompt &key (system-prompt "You are the Probabilistic engine of a Probabilistic-Deterministic Lisp Machine.") (cascade nil) (context nil))
"Dispatches a neural request through the provider cascade or parallel consensus."
(let ((backends (cond
((and cascade (listp cascade)) cascade)
((functionp cascade) (funcall cascade context))
(t *provider-cascade*))))
(if *consensus-enabled-p*
;; PARALLEL CONSENSUS MODE
(let ((results nil)
(threads nil)
(lock (bt:make-lock)))
(dolist (backend backends)
(let ((backend-fn (gethash backend *probabilistic-backends*)))
(when backend-fn
(push (bt:make-thread
(lambda ()
(harness-log "PROBABILISTIC [Consensus]: Querying backend ~a..." backend)
(let* ((model (when *model-selector-fn* (funcall *model-selector-fn* backend context)))
(result (ignore-errors
(if model
(funcall backend-fn prompt system-prompt :model model)
(funcall backend-fn prompt system-prompt)))))
(bt:with-lock-held (lock)
(push result results)))))
threads))))
;; Wait for all threads with a timeout (e.g., 30s)
(let ((start-time (get-universal-time)))
(loop while (and (< (length results) (length threads))
(< (- (get-universal-time) start-time) 30))
do (sleep 0.1)))
;; Return the list of raw results (filtering out nils or errors)
(let ((valid-results (remove-if-not #'stringp results)))
(if valid-results
(format nil "~{~a~^|CONSENSUS-SEP|~}" valid-results)
"(:type :LOG :payload (:text \"Neural Consensus Failure\"))")))
;; SEQUENTIAL CASCADE MODE
(or (dolist (backend backends)
(let ((backend-fn (gethash backend *probabilistic-backends*)))
(when backend-fn
(harness-log "PROBABILISTIC: Attempting backend ~a..." backend)
(let* ((model (when *model-selector-fn* (funcall *model-selector-fn* backend context)))
(result (if model
(funcall backend-fn prompt system-prompt :model model)
(funcall backend-fn prompt system-prompt))))
(unless (or (null result)
(and (stringp result) (search ":LOG" result) (or (search "Failure" result) (search "missing" result))))
(return result))))))
"(:type :LOG :payload (:text \"Neural Cascade Failure\"))"))))
(defun think (context)
"Invokes the neural Probabilistic engine to propose a Lisp action based on context."
(let ((active-skill (find-triggered-skill context))
(tool-belt (generate-tool-belt-prompt))
(global-context (context-assemble-global-awareness)))
(if active-skill
(progn
(harness-log "PROBABILISTIC: Engaging skill '~a'~%" (skill-name active-skill))
(let* ((prompt-generator (skill-probabilistic-prompt active-skill))
(raw-prompt (when prompt-generator (funcall prompt-generator context)))
(full-system-prompt (concatenate 'string
"ACTUATOR IDENTITY: You are the pure Lisp actuator for the opencortex kernel.
MANDATE: Output EXACTLY ONE Common Lisp property list starting with (:type :REQUEST).
ZERO CONVERSATION: Do not explain. Do not say 'Okay'. Do not use markdown blocks.
STRICT RULE: Do not output multiple lists. Do not chain multiple requests.
DO NOT embed tool calls inside text strings.
"
global-context
"
"
tool-belt
"
IMPORTANT: To reply to the user, you MUST use:
(:type :REQUEST :target :emacs :action :insert-at-end :buffer \"*opencortex-chat*\" :text \"* <Response Text>\")
To call a tool, you MUST use:
(:type :REQUEST :target :tool :action :call :tool \"<name>\" :args (:arg1 \"val\"))
")))
(if (and raw-prompt (> (length raw-prompt) 1))
(let* ((thought (ask-probabilistic raw-prompt :system-prompt full-system-prompt :context context))
(raw-thoughts (cl-ppcre:split (cl-ppcre:quote-meta-chars "|CONSENSUS-SEP|") thought))
(suggestions nil))
(dolist (raw-thought raw-thoughts)
(harness-log "PROBABILISTIC RAW: ~a~%" raw-thought)
(let* ((cleaned-thought
(let ((match (cl-ppcre:scan-to-strings "(?s)```(?:lisp)?\\n?(.*?)\\n?```" raw-thought)))
(if match
(let ((regs (nth-value 1 (cl-ppcre:scan-to-strings "(?s)```(?:lisp)?\\n?(.*?)\\n?```" raw-thought))))
(if (and regs (> (length regs) 0)) (elt regs 0) raw-thought))
(string-trim '(#\Space #\Newline #\Tab) raw-thought))))
(suggestion (handler-case (read-from-string cleaned-thought)
(error (c)
;; EMIT ASYNCHRONOUS REPAIR STIMULUS
(list :type :EVENT :payload
(list :sensor :syntax-error
:code cleaned-thought
:error (format nil "~a" c)))))))
(harness-log "PROBABILISTIC Suggestion: ~a~%" cleaned-thought)
(when (and suggestion (listp suggestion))
(push suggestion suggestions))))
(if (and *consensus-enabled-p* suggestions)
(nreverse suggestions)
(first (nreverse suggestions))))
'(:type :LOG :payload (:text "Skill triggered (Deterministic only)")))))
nil)))
(defun distill-prompt (full-prompt successful-output)
(let ((system-instr "You are a Meta-Cognitive Prompt Architect. DISTILL into template."))
(ask-probabilistic (format nil "PROMPT: ~a~%RESULT: ~a" full-prompt successful-output) :system-prompt system-instr)))

Some files were not shown because too many files have changed in this diff Show More