ollie/doc/IDEAS.md

192 lines
9.0 KiB
Markdown

## Ideas
### Automation & Scripting
**Scripting and automation** — shell scripts that submit prompts, poll `state` until idle, then read `chat` for the result. No SDK, no HTTP client, just file I/O. Works in any language that can write to a file.
**Multiplexing sessions** — run several agents in parallel, each in their own session directory, and fan work out to them from a shell script. Coordinate by watching their `state` files.
**Hooks and watchers** — poll `state` in a loop to trigger actions when the agent finishes, or use `tail -f chat` to react to new output. Build lightweight event-driven pipelines with standard shell tools.
### Tooling & Integration
**Unix tools** — pipe `chat` into `grep`, `awk`, `sed`. Diff two sessions' outputs. Log conversations with `cp`. Search history across sessions with `grep -r`.
**Editor integration** — any editor that can read/write files gets agent access for free. In `acme` or `sam`, you write to `prompt` and read back `chat` with no plugin required. Fits naturally into the Plan 9 workflow.
**Lightweight TUI alternatives** — `watch cat state` as a status bar, `tail -f chat` in one pane, prompt submission in another. Compose a working interface entirely from standard tools.
### Network & Remote Access
**Remote access** — 9P is a network protocol. Export the namespace over the network and access agent sessions from another machine using the same file interface, with no additional daemon or API layer.
### Multi-Agent Workflows
Each session exposes a `reply` file containing only the assistant text from the most recently completed turn. Writing to `prompt` clears `reply` as a side-effect. Together these make agent-to-agent handoffs expressible as plain file operations — no message queue, no orchestration framework, no shared memory.
A shell script moves text between sessions. The agents communicate only through what the script explicitly passes between them, so information flow is fully under your control. Each agent can use a different backend, model, or tool configuration. A human can intervene at any point by writing directly to a session's `prompt`.
#### Example: develop → review → test
Three sessions with specialized agent configs run a feedback loop. The reviewer ends its response with `LGTM` (proceed) or `PTAL` (revise). The tester ends with `Approved` (done) or `Rejected` (revise). A shell script reads the verdict with `grep` and routes accordingly.
```sh
#!/bin/sh
cwd=${1:?usage: $0 <cwd>}
cd ~/mnt/ollie
# Count existing sessions so we can identify the newly created ones by offset.
n=$(ls s/ | wc -l)
echo "new agent=developer cwd=$cwd" > ctl
echo "new agent=reviewer cwd=$cwd" > ctl
echo "new agent=tester cwd=$cwd" > ctl
dev=$(ls s/ | sort | sed -n "$((n+1))p")
rev=$(ls s/ | sort | sed -n "$((n+2))p")
tst=$(ls s/ | sort | sed -n "$((n+3))p")
wait_reply() {
while [ "$(wc -c < s/$1/reply)" -eq 0 ]; do sleep 1; done
}
echo "implement a function that parses a JSON config file" > s/$dev/prompt
while true; do
wait_reply $dev
code=$(cat s/$dev/reply)
# review phase
printf "Review the following code. End your response with LGTM if it is ready, or PTAL if it needs revision.\n\n%s" "$code" > s/$rev/prompt
wait_reply $rev
if ! grep -qi "LGTM" s/$rev/reply; then
{ echo "revise this code based on the feedback below."
echo "--- code ---"; echo "$code"
echo "--- feedback ---"; cat s/$rev/reply
} > s/$dev/prompt
continue
fi
# test phase
printf "Test the following code. End your response with Approved if all tests pass, or Rejected if they do not.\n\n%s" "$code" > s/$tst/prompt
wait_reply $tst
if grep -qi "Approved" s/$tst/reply; then
echo "done."
echo "$code"
break
fi
{ echo "fix the failures reported below."
echo "--- code ---"; echo "$code"
echo "--- test report ---"; cat s/$tst/reply
} > s/$dev/prompt
done
```
Each agent only sees what the script explicitly sends it. Each can use a different backend, model, or tool configuration. A human can intervene at any point by writing directly to a session's `prompt`. The boundary between fully automated and human-in-the-loop is just whether the script pauses to ask.
### Sub-Agents
A session can spawn ephemeral child sessions to handle a focused task, then tear them down when done. The parent uses `execute_code` to write `new` to `ctl`, send an initial context and task to the child's `prompt`, poll until `reply` is ready, read the result, and write `kill <id>` to `ctl`. From the child's perspective it is just a normal session; it has no knowledge of its own ephemerality.
**Sub-agents as function calls** — the parent primes the child with exactly the context it needs (excerpts from its own `chat`, relevant beads, local file contents) and receives a single focused reply. Failures are isolated; a child that errors or stalls can be killed and retried without affecting the parent. The pattern composes naturally: a sub-agent can itself spawn sub-agents, building a call tree bounded only by the number of sessions open at once.
`spawn_subagents.py` reads a JSON spec from stdin, fans out the work concurrently, and returns a JSON array of results — a single `execute_tool` call from the parent's perspective. Note: this requires adding Python language support to `execute_code`, `execute_tool`, and `execute_pipe`, which is low effort.
```python
#!/usr/bin/env python3
"""spawn_subagents.py — fan out tasks to ephemeral olliesrv sub-agents.
Input (stdin): JSON array of {"task": str, "agent": str, "context": str}
Output (stdout): JSON array of {"agent": str, "reply": str}
Install in ~/.config/ollie/tools/ to make available via execute_tool.
"""
import json, os, sys, threading, time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
BASE = Path.home() / "mnt" / "ollie"
POLL = 0.5
_spawn_lock = threading.Lock()
def session_ids():
return {e.name for e in (BASE / "s").iterdir() if e.is_dir()}
def spawn_session(agent="default", cwd=""):
with _spawn_lock:
before = session_ids()
(BASE / "ctl").write_text(f"new agent={agent} cwd={cwd}\n")
while True:
new = session_ids() - before
if new:
return new.pop()
time.sleep(0.1)
def kill_session(sid):
(BASE / "ctl").write_text(f"kill {sid}\n")
def wait_reply(sid):
path = BASE / "s" / sid / "reply"
while True:
if path.stat().st_size > 0:
return path.read_text().strip()
time.sleep(POLL)
def run_subagent(spec):
agent = spec.get("agent", "default")
cwd = spec.get("cwd", "")
context = spec.get("context", "")
task = spec.get("task", "")
sid = spawn_session(agent, cwd)
try:
prompt = f"{context}\n\n{task}".strip() if context else task
(BASE / "s" / sid / "prompt").write_text(prompt)
return {"agent": agent, "reply": wait_reply(sid)}
finally:
kill_session(sid)
def main():
specs = json.load(sys.stdin)
results = [None] * len(specs)
with ThreadPoolExecutor(max_workers=len(specs)) as pool:
futures = {pool.submit(run_subagent, s): i for i, s in enumerate(specs)}
for f in as_completed(futures):
results[futures[f]] = f.result()
json.dump(results, sys.stdout, indent=2)
print()
if __name__ == "__main__":
main()
```
The parent agent constructs the spec and makes a single tool call:
```json
[
{ "task": "review this code for correctness", "agent": "reviewer", "context": "..." },
{ "task": "check this code for security issues", "agent": "security", "context": "..." }
]
```
Results come back as:
```json
[
{ "agent": "reviewer", "reply": "..." },
{ "agent": "security", "reply": "..." }
]
```
### Self-Generating Workflows
Since agents have access to `execute_code`, a session can write and execute a workflow script without any human involvement. Given a task and knowledge of the filesystem layout, an agent can decompose the work, spawn sessions, write the coordination script, and run it — all in a single turn. The README you are reading is essentially its system prompt. Conductor functionality, for free.
### Persistent Memory
**Memory Recall** — Agent sessions are ephemeral by nature, but some knowledge should outlive them. Memory recall gives an agent the ability to persist and retrieve facts across sessions via two abstract tool contracts: `memory_remember` and `memory_recall`. When the agent learns something worth keeping — a user preference, an architectural decision, a hard-won debugging insight — it calls `memory_remember` with a concise, self-authored summary. On a future session it calls `memory_recall` to retrieve relevant memories before reasoning. Memories are LLM-generated rather than raw chat logs: the agent decides what matters and phrases it canonically, so memories remain meaningful long after the conversation that produced them is gone. The tool contract follows the same pattern as `reasoning_plan`: the agent owns the content, the tool owns the storage.