Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
40964dc
feat(expert-shell): add package
lost-rob0t Sep 17, 2026
915e727
feat(expert-shell): add session and fact model
lost-rob0t Sep 17, 2026
77868c9
feat(expert-shell): add Lisa planning rules
lost-rob0t Sep 17, 2026
fea4182
feat(expert-shell): implement Lisa-backed operator shell
lost-rob0t Sep 17, 2026
e12819f
feat(expert-shell): add ASDF system
lost-rob0t Sep 17, 2026
0581e6f
build: add Lisa expert-system dependency
lost-rob0t Sep 17, 2026
a84ab64
build: lock Lisa dependency
lost-rob0t Sep 17, 2026
df23f30
test(expert-shell): cover Lisa planning and safety gates
lost-rob0t Sep 17, 2026
c2b1eac
test(expert-shell): register expert shell system and suite
lost-rob0t Sep 17, 2026
99ff333
test(expert-shell): require expert shell suite
lost-rob0t Sep 17, 2026
6c3f48a
docs(expert-shell): document Lisa operator shell
lost-rob0t Sep 17, 2026
c6f1608
test(expert-shell): keep shell tests isolated from server test closure
lost-rob0t Sep 17, 2026
f9059a3
test(expert-shell): keep server required suites unchanged
lost-rob0t Sep 17, 2026
08f66d4
test(expert-shell): make suite standalone
lost-rob0t Sep 17, 2026
01bbcdb
test(expert-shell): add standalone ASDF test system
lost-rob0t Sep 17, 2026
3a0fbf1
chore: drop unrelated test-system diff
lost-rob0t Sep 17, 2026
334bc6e
chore: drop unrelated unit-runner diff
lost-rob0t Sep 17, 2026
577034c
ci(expert-shell): run Lisa shell tests and build
lost-rob0t Sep 17, 2026
213ef57
ci(expert-shell): bootstrap Lisp with setup-lisp
lost-rob0t Sep 17, 2026
81ec0ec
ci(expert-shell): preload shell test system
lost-rob0t Sep 17, 2026
4302f5e
ci(expert-shell): use Qlot 1.8 installer with fixed bin path
lost-rob0t Sep 17, 2026
2544c74
ci(expert-shell): let qlot install be the health check
lost-rob0t Sep 17, 2026
0c8848b
feat(expert-shell): expand read plans and preserve client error seman…
lost-rob0t Sep 17, 2026
c87a451
fix(expert-shell): harden parsing state and one-shot argv semantics
lost-rob0t Sep 17, 2026
57ea3be
fix(expert-shell): repair plist JSON serializer
lost-rob0t Sep 17, 2026
45bbd72
test(expert-shell): cover hardened parser and operator semantics
lost-rob0t Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/expert-shell.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Expert Shell

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

permissions:
contents: read

concurrency:
group: expert-shell-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lisa-shell:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Common Lisp build prerequisites
run: |
sudo apt-get update
sudo apt-get install --yes \
sbcl \
libssl-dev \
libffi-dev \
librabbitmq-dev \
pkg-config

- name: Install Qlot 1.8
shell: bash
run: |
set -euo pipefail
curl --fail --location --show-error https://qlot.tech/installer \
| QLOT_BIN_DIR="$HOME/.qlot/bin" sh
test -x "$HOME/.qlot/bin/qlot"
echo "$HOME/.qlot/bin" >> "$GITHUB_PATH"

- name: Install locked Lisp dependencies
shell: bash
run: |
set -euo pipefail
qlot install

- name: Run expert-shell tests
shell: bash
run: |
set -euo pipefail
qlot exec sbcl --non-interactive --no-userinit --no-sysinit \
--eval '(require :asdf)' \
--eval '(asdf:test-system :starintel-expert-shell-tests)'

- name: Build star-expert executable
shell: bash
run: |
set -euo pipefail
qlot exec sbcl --non-interactive --no-userinit --no-sysinit \
--eval '(require :asdf)' \
--eval '(asdf:make :starintel-expert-shell)'
test -x star-expert
133 changes: 133 additions & 0 deletions doc/expert-shell.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#+title: StarIntel Lisa Expert Shell
#+author: StarIntel

* Purpose

=starintel-expert-shell= is a Common Lisp operator shell that routes common
StarIntel operations through a Lisa forward-chaining inference engine before
calling the normal StarIntel HTTP client boundary.

The shell is intentionally not a privileged back door. Authentication,
authorization, tenant handling, rate limits, and server-side validation remain
owned by the existing StarIntel API.

* Model

Each shell session owns its own Lisa inference engine and four useful values:

- a =shell-request= fact parsed from operator input;
- a =shell-plan= fact inferred by Lisa;
- a =shell-result= returned by the client boundary;
- an append-only in-memory trace describing request, rule, risk, and result.

A request is classified as one of three risk levels:

| Risk | Behavior |
|------+----------|
| =:read= | Executes immediately |
| =:write= | Requires explicit confirmation |
| =:destructive= | Requires explicit confirmation |

The confirmation gate is itself expressed as Lisa rules. It is not merely a
check hidden in the command parser.

* Build

With Qlot dependencies installed:

#+begin_src sh
qlot exec sbcl --non-interactive \
--eval '(require :asdf)' \
--eval '(asdf:make :starintel-expert-shell)'
#+end_src

The ASDF program output is =star-expert=.

The client defaults to =http://127.0.0.1:5000=. Set =STAR_SERVER_URL= and
=STAR_API_KEY= or pass =--url= and =--api-key= explicitly.

* Interactive use

#+begin_src sh
./star-expert
#+end_src

Example session:

#+begin_example
star> health
star> info
star> doc search "alice smith" --limit 10
star> target list userhunt
star> dataset size social-posts
star> why
#+end_example

* One-shot use

#+begin_src sh
./star-expert health
./star-expert doc get 01HXYZ
./star-expert doc search "alice smith" --limit 10
./star-expert --json health
#+end_src

Mutating operations require =--yes=:

#+begin_src sh
./star-expert doc submit person '{"name":"Alice"}' --yes
./star-expert target create userhunt '{"username":"alice"}' --yes
./star-expert doc delete 01HXYZ --yes
#+end_src

Without =--yes=, Lisa produces a =:confirmation-required= result and the HTTP
transport is never invoked.

* Commands

| Command | Inferred operation | Risk |
|---------+--------------------+------|
| =health=, =status= | server health | read |
| =info=, =server info= | server metadata | read |
| =doc get ID= | fetch document | read |
| =doc search QUERY= | full-text search | read |
| =doc submit TYPE JSON= | submit document | write |
| =doc delete ID= | delete document | destructive |
| =target list ACTOR= | list actor targets | read |
| =target get ID= | fetch target document | read |
| =target create ACTOR JSON= | submit target | write |
| =dataset size NAME= | dataset size | read |
| =groups= | message groups/channels | read |
| =messages user USER= | messages by user | read |
| =messages platform PLATFORM= | messages by platform | read |
| =messages group= | grouped messages | read |
| =social user USER= | social posts by user | read |
| =raw get PATH= | raw client GET | read |
| =raw post PATH BODY= | raw client POST | write |
| =raw put PATH BODY= | raw client PUT | write |
| =raw delete PATH= | raw client DELETE | destructive |

=raw= is an operator escape hatch but still uses =star.api.client:api-request=
and therefore the normal StarIntel HTTP boundary.

* Lisp command syntax

Commands may also be written as data-oriented Lisp forms:

#+begin_src lisp
(doc search "alice smith" :limit 10)
(target create userhunt "{\"username\":\"alice\"}" :transient :yes)
#+end_src

These forms are *read as data*. They are never passed to =eval=, and the
reader binds =*read-eval*= to =nil= so =#.=-style read-time execution is
rejected.

* Explanation

After a command, =why= reports the Lisa rule that selected the operation, its
risk class, the human-readable reason, and the session trace.

The intent is to make this a stable reasoning surface for Quasar and Zara later:
those front ends can submit the same structured request facts and consume the
same plans/results without scraping terminal output.
110 changes: 110 additions & 0 deletions expert-shell/model.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
(in-package :star.expert.shell)

(defclass shell-session ()
((client
:initarg :client
:reader shell-session-client)
(engine
:initarg :engine
:reader shell-session-engine)
(last-result
:initform nil
:accessor shell-session-last-result)
(last-plan
:initform nil
:accessor shell-session-last-plan)
(trace
:initform nil
:accessor shell-session-trace)))

(defclass shell-request ()
((verb
:initarg :verb
:reader shell-request-verb)
(resource
:initarg :resource
:reader shell-request-resource)
(qualifier
:initarg :qualifier
:initform nil
:reader shell-request-qualifier)
(args
:initarg :args
:initform nil
:reader shell-request-args)
(confirmed-p
:initarg :confirmed-p
:initform nil
:reader shell-request-confirmed-p)
(raw
:initarg :raw
:initform nil
:reader shell-request-raw)))

(defclass shell-plan ()
((operation
:initarg :operation
:reader shell-plan-operation)
(risk
:initarg :risk
:reader shell-plan-risk)
(rule-name
:initarg :rule-name
:reader shell-plan-rule-name)
(reason
:initarg :reason
:reader shell-plan-reason)
(args
:initarg :args
:initform nil
:reader shell-plan-args)
(confirmed-p
:initarg :confirmed-p
:initform nil
:reader shell-plan-confirmed-p)))

(defclass shell-result ()
((success-p
:initarg :success-p
:reader shell-result-success-p)
(operation
:initarg :operation
:initform nil
:reader shell-result-operation)
(code
:initarg :code
:initform nil
:reader shell-result-code)
(value
:initarg :value
:initform nil
:reader shell-result-value)
(message
:initarg :message
:initform nil
:reader shell-result-message)))

(defvar *current-session* nil)

(defun trace-event (event &rest fields)
(when *current-session*
(push (list* :event event fields)
(shell-session-trace *current-session*))))

(defun finish-result (result)
(unless *current-session*
(error "No active StarIntel expert-shell session"))
(setf (shell-session-last-result *current-session*) result)
(trace-event :result
:success-p (shell-result-success-p result)
:operation (shell-result-operation result)
:code (shell-result-code result))
result)

(defun make-result (&key success-p operation code value message)
(make-instance 'shell-result
:success-p success-p
:operation operation
:code code
:value value
:message message))
35 changes: 35 additions & 0 deletions expert-shell/package.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
(uiop:define-package :star.expert.shell
(:use :cl)
(:export
#:shell-session
#:shell-session-client
#:shell-session-engine
#:shell-session-last-result
#:shell-session-last-plan
#:shell-session-trace
#:shell-request
#:shell-request-verb
#:shell-request-resource
#:shell-request-qualifier
#:shell-request-args
#:shell-request-confirmed-p
#:shell-request-raw
#:shell-plan
#:shell-plan-operation
#:shell-plan-risk
#:shell-plan-rule-name
#:shell-plan-reason
#:shell-result
#:shell-result-success-p
#:shell-result-operation
#:shell-result-code
#:shell-result-value
#:shell-result-message
#:make-shell-session
#:parse-command
#:run-command
#:render-result
#:start-repl
#:main))

(in-package :star.expert.shell)
Loading
Loading