diff --git a/README.md b/README.md index b33be49..65bb971 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ -# Ollie Monorepo +# ollie -This repository contains all Ollie projects as submodules: +What happens if agent primitives are exposed as an ordinary filesystem? How much can be subtracted from AI agent implementations without reducing their usefulness? These are some of the motivating questions behind `ollie`. -- **core/** – The core Ollie agent library ([ollie-core](https://github.com/lneely/ollie-core)) -- **tui/** – Terminal UI frontend ([ollie-tui](https://github.com/lneely/ollie-tui)) -- **9p/** – 9p filesystem server ([ollie-9p](https://github.com/lneely/ollie-9p)) -- **el/** – Emacs Lisp interface ([ollie-el](https://github.com/lneely/ollie-el)) +- The [core system](https://github.com/lneely/ollie-core) provides a public library of AI agent primitives. +- The [9p filesystem server (Go)](https://github.com/lneely/ollie-9p) creates a virtual filesystem exposing the important state and behaviors as ordinary files and operations. +- A reference [TUI](https://github.com/lneely/ollie-tui) implementation proves that it is possible to build a functioning and user-friendly terminal front-end using only the Go standard library. +- And, just because I like my Emacs friends, a reference [Emacs interface](https://github.com/lneely/ollie-el) front-end implementation. + +## Project Structure + +- `core/`: +- `tui/`: +- `9p/`: +- `el/`: ## Getting Started @@ -37,7 +44,11 @@ mk tui # Build terminal UI mk 9p # Build 9p filesystem server ``` -For Emacs Lisp, load `el/ollie.el` in Emacs. +For Emacs Lisp, copy `el/ollie.el` into your own Emacs configuration directory, and: + +```lisp +(require 'ollie) +``` ## Development @@ -47,20 +58,6 @@ Update all submodules to latest: git submodule update --remote ``` -## Project Links - -- **Monorepo**: [github.com/lneely/ollie](https://github.com/lneely/ollie) -- **Core Library**: [github.com/lneely/ollie-core](https://github.com/lneely/ollie-core) -- **Terminal UI**: [github.com/lneely/ollie-tui](https://github.com/lneely/ollie-tui) -- **9p Server**: [github.com/lneely/ollie-9p](https://github.com/lneely/ollie-9p) -- **Emacs Lisp**: [github.com/lneely/ollie-el](https://github.com/lneely/ollie-el) - -## Project Structure - -- `core/`: Core agent library (Go) -- `tui/`: Terminal UI (Go) -- `9p/`: 9p filesystem server (Go) -- `el/`: Emacs Lisp interface (Emacs Lisp) ## Credits Many sources of inspiration: diff --git a/toys/ollie.sh b/toys/ollie.sh new file mode 100755 index 0000000..d51a4cb --- /dev/null +++ b/toys/ollie.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# ollie.sh: a simplified, fully-functional chat interface for ollie AI agents. +set -o history + +MOUNT=${OLLIE_9MOUNT:-$HOME/mnt/ollie} +LAST=$HOME/.config/ollie/last-session + +die() { printf 'error: %s\n' "$*" >&2; exit 1; } +is_idle() { [ "$(tr -d '[:space:]' < "$S/state" 2>/dev/null)" = "idle" ]; } +drain() { + local n + n=$(wc -c < "$S/chat" 2>/dev/null || echo 0) + [ "$n" -gt "$chat_off" ] && tail -c "+$((chat_off+1))" "$S/chat" | head -c "$((n-chat_off))" + chat_off=$n +} + +# Parse flags +new=0; resume=0; id_flag= +backend=; model=; agent=; workdir= +while [ $# -gt 0 ]; do + case $1 in + -new) new=1 ;; + -resume) resume=1 ;; + -session) id_flag=$2; shift ;; + -backend) backend=$2; shift ;; + -model) model=$2; shift ;; + -agent) agent=$2; shift ;; + -workdir) workdir=$2; shift ;; + *) die "unknown flag: $1" ;; + esac + shift +done + +# Build "new" ctl command with opts +new_cmd() { + local cmd=new + [ -n "$backend" ] && cmd="$cmd backend=$backend" + [ -n "$model" ] && cmd="$cmd model=$model" + [ -n "$agent" ] && cmd="$cmd agent=$agent" + cmd="$cmd workdir=${workdir:-$(pwd)}" + printf '%s\n' "$cmd" +} + +# Create a fresh session, sets $id +create_session() { + local before d deadline + before=$(ls "$MOUNT/s" 2>/dev/null) + new_cmd > "$MOUNT/ctl" || die "write ctl" + deadline=$(($(date +%s) + 5)) + while [ "$(date +%s)" -lt "$deadline" ]; do + for d in $(ls "$MOUNT/s" 2>/dev/null); do + printf '%s\n' "$before" | grep -qxF "$d" || { id=$d; return; } + done + sleep 0.05 + done + die "timeout waiting for new session" +} + +# Resolve session +if [ -n "$id_flag" ]; then + [ -d "$MOUNT/s/$id_flag" ] || die "session not found: $id_flag" + id=$id_flag; printf 'session: %s (resumed)\n' "$id" >&2 +elif [ "$resume" = 1 ]; then + id=$(cat "$LAST" 2>/dev/null | tr -d '[:space:]') + [ -n "$id" ] && [ -d "$MOUNT/s/$id" ] || die "no resumable session" + printf 'session: %s (resumed)\n' "$id" >&2 +elif [ "$new" = 1 ]; then + create_session + printf '%s\n' "$id" > "$LAST" + printf 'session: %s\n' "$id" >&2 +else + id=$(cat "$LAST" 2>/dev/null | tr -d '[:space:]') + if [ -n "$id" ] && [ -d "$MOUNT/s/$id" ]; then + printf 'session: %s (resumed)\n' "$id" >&2 + else + create_session + printf '%s\n' "$id" > "$LAST" + printf 'session: %s\n' "$id" >&2 + fi +fi + +S=$MOUNT/s/$id +chat_off=$(wc -c < "$S/chat" 2>/dev/null || echo 0) + +trap ' + [ "$(tr -d "[:space:]" < "$S/state" 2>/dev/null)" = "idle" ] && exit + printf "\n" >&2; printf "stop\n" > "$S/ctl" +' INT + +while true; do + printf '> ' + IFS= read -re input || break + [ -z "$input" ] && continue + history -s "$input" + case "$input" in + /kill) printf 'kill %s\n' "$id" > "$MOUNT/ctl"; break ;; + "/q "*) printf '%s\n' "${input#/q }" > "$S/enqueue" + printf 'queued\n' >&2; continue ;; + /*) printf '%s\n' "$input" > "$S/ctl" ;; + *) printf '%s' "$input" > "$S/prompt" || { printf 'submit failed\n' >&2; continue; } ;; + esac + sleep 0.15 + while true; do + drain + is_idle && { drain; printf '\n'; break; } + sleep 0.05 + done +done