Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ The harness ships a marketplace of [plugins](plugins/) (Claude Code / Pi skills,
└── a Gap becomes ──┘
a hypothesis
trail ── records goal · hypothesis · alternatives · outcome (git trailers)
atlas ── indexes every artefact; its frontier seeds the next run (the loop)
tend ── runs the checks on a cadence and self-heals (keeps it running)
```

| Stage | Command | What it does |
Expand All @@ -73,6 +75,9 @@ The harness ships a marketplace of [plugins](plugins/) (Claude Code / Pi skills,
| **do** | `/investigate` | *Run* the question, don't read about it. **Frame** it into 2-5 falsifiable hypotheses, **fan out** one contained agent per hypothesis (the swarm), **judge** the outcomes adversarially against their evidence, **record** everything to the trail. |
| **show** | `/present` | Turn a result into a served, visual presentation: pick the medium per finding (Manim animation · marimo app · static figure · served notebook), render on the right compute, reachable over Tailscale. |
| **trail** | `trail` | The decision graph: hypotheses, choices, roads not taken, and outcomes recorded as git-commit trailers. Reconstructs from `git log` alone (no LLM), and renders as a B&W graph + tempo timeline with open hypotheses flagged as awaiting a verdict. |
| **do (quantitative)** | `/modeling` | *Do* the quantitative science: turn data (a file, a simulation, or a digitized figure) into a fitted, model-selected, uncertainty-quantified law: candidate models, parameter covariance, AIC/BIC + cross-validation, symbolic regression. The quantitative twin of `/investigate`; composes the Axiomatic tools. |
| **compound** | `atlas` | The corpus that makes the arc *compound*: indexes every study, investigation, map and trail into one B&W page and collects their gaps and frontiers into one queue the next run pulls from. The agent arXiv. |
| **continuity** | `tend` | The layer that turns the arc from a line into a *loop*: register checks that should keep passing, run them on a cadence, record drift, and (armed) spawn a contained agent to self-heal, so work stays healthy and the frontier re-opens without a human in the hot path. |

```bash
/study arxiv.org/abs/2601.06712 # understand: a paper → dossier in the atlas
Expand All @@ -82,7 +87,7 @@ trail # see the decision graph reconstruct fr
/present # show: render the surviving result, serve it
```

`/delve` conducts understand→show in one shot; `/map` and `/explore` render a whole repo into a one-page dossier. The full set lives in **[`plugins/`](plugins/)**: `science-writing` (verify-citations, peer-review, rebuttal, arxiv-prep), `tikz`, `manim`, `marimo`, and `writing-styles`.
`/delve` conducts understand→show in one shot; `/map` and `/explore` render a whole repo into a one-page dossier. `atlas` indexes the whole corpus so the frontier of one run seeds the next, and `tend` keeps the checks healthy over time; together they close the arc into a loop. The full set lives in **[`plugins/`](plugins/)**: `science-writing` (verify-citations, peer-review, rebuttal, arxiv-prep), `tikz`, `manim`, `marimo`, `writing-styles`, and `modeling` (quantitative fits).

---

Expand Down Expand Up @@ -345,7 +350,7 @@ When `anu init` finds an existing config it offers three strategies: **merge** (
<details>
<summary><b>State & data</b></summary>

anu exposes the repo at `~/.local/share/anu/` and stores runtime state there: `swarms/` (metadata, mailboxes), `reviews/` (cached summaries per SHA), `mesh/` (device cache), `box/` (contained-agent state; `box/claude` holds credentials, gitignored). The **atlas** at `~/.anu/atlas/<repo>` holds dossiers and investigations; the decision **trail** at `~/.anu/trail/<repo>`. The installer link manifest lives at `~/.local/state/anu/manifest` so `anu unlink` restores configs cleanly. All runtime state is gitignored; never commit it.
anu exposes the repo at `~/.local/share/anu/` and stores runtime state there: `swarms/` (metadata, mailboxes), `reviews/` (cached summaries per SHA), `mesh/` (device cache), `box/` (contained-agent state; `box/claude` holds credentials, gitignored). The **atlas** at `~/.anu/atlas/<repo>` holds dossiers and investigations, with `atlas` rendering one index over all of it at `~/.anu/atlas/index.html`; the decision **trail** at `~/.anu/trail/<repo>`; and `tend` keeps watch state under `~/.local/share/anu/tend/`. The installer link manifest lives at `~/.local/state/anu/manifest` so `anu unlink` restores configs cleanly. All runtime state is gitignored; never commit it.

</details>

Expand Down
7 changes: 6 additions & 1 deletion config/bash/bin/swarm
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#!/usr/bin/env bash
# Standalone wrapper so `swarm` is callable as a command (not just a function)
# This lets AI agents inside Claude Code run `swarm send`, `swarm collect`, etc.
source "${ANU_PATH:-$HOME/.local/share/anu}/config/bash/fns/swarm"
ANU="${ANU_PATH:-$HOME/.local/share/anu}"
# swarm() guards its deps through core (_anu_require); source core first, exactly
# as bin/tend does, or every non-help subcommand dies here with
# "_anu_require: command not found" on this non-interactive path.
source "$ANU/config/bash/fns/core"
source "$ANU/config/bash/fns/swarm"
swarm "$@"
11 changes: 11 additions & 0 deletions config/bash/bin/tend
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Standalone wrapper so `tend` is callable as a command, not just a shell
# function, needed by the cron/launchd heartbeat (`tend cron on`), which runs
# in a shell that has not sourced the anu fns. Mirrors bin/box, bin/delve, bin/swarm.
ANU="${ANU_PATH:-$HOME/.local/share/anu}"
# cron/launchd runs with a minimal PATH; make anu's tools resolvable (box/cxc for
# the heal, jq, homebrew bins) so the headless heartbeat and its healer can run.
export PATH="$ANU/config/bash/bin:$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
source "$ANU/config/bash/fns/core"
source "$ANU/config/bash/fns/tend"
tend "$@"
50 changes: 50 additions & 0 deletions config/bash/fns/atlas
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# ==============================================================================
# atlas: one word, render the corpus index across all anu research artifacts.
#
# Every /study, /investigate, /map and trail render lands under ~/.anu/atlas
# (and ~/.anu/trail). Each is a one-off today. `atlas` indexes them into one
# fixed B&W page and collects their gaps, open questions and frontiers into a
# single queue, so finished work exposes what it left open and the next
# investigation pulls its question from there. The corpus compounds; the agent
# arXiv. No LLM: the index IS the on-disk JSON (build.py + render.py).
#
# atlas build + render + open the corpus index
# atlas open re-open the last index without rebuilding
# atlas ls list the corpus records in the terminal
#
# The skill `atlas` (plugins/atlas) teaches agents to consult the frontier
# before starting work and to leave their own gaps behind when they finish.
# ==============================================================================

_atlas_render() { # <atlas_root> <trail_root> <skill>
python3 "$3/build.py" "$1" "$2" "$1/atlas.json" >/dev/null &&
python3 "$3/render.py" "$1/atlas.json" "$1/index.html" >/dev/null
}

atlas() {
local atlas_root="$HOME/.anu/atlas" trail_root="$HOME/.anu/trail" skill
skill="${ANU_PATH:-$HOME/.local/share/anu}/plugins/atlas/skills/atlas"

case "${1:-}" in
open)
if [[ -f "$atlas_root/index.html" ]]; then
open "$atlas_root/index.html" 2>/dev/null || xdg-open "$atlas_root/index.html" 2>/dev/null
else
echo "no atlas yet. Run: atlas"
fi
return ;;
ls)
_anu_require python3 jq || return 1
mkdir -p "$atlas_root"
[[ -f "$atlas_root/atlas.json" ]] || _atlas_render "$atlas_root" "$trail_root" "$skill" >/dev/null
jq -r '.records[] | " [\(.kind)]\t\(.title)"' "$atlas_root/atlas.json" 2>/dev/null \
|| echo "no atlas yet. Run: atlas"
return ;;
esac

_anu_require python3 || return 1
mkdir -p "$atlas_root"
_atlas_render "$atlas_root" "$trail_root" "$skill" || { _anu_die "atlas: build failed"; return 1; }
echo "atlas → $atlas_root/index.html"
open "$atlas_root/index.html" 2>/dev/null || xdg-open "$atlas_root/index.html" 2>/dev/null || true
}
48 changes: 48 additions & 0 deletions config/bash/fns/core
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ==============================================================================
# core: shared primitives for anu shell functions.
# ==============================================================================
# Sourced into every interactive shell (via the config/bash/fns/* glob, before
# the commands that call it run) and by the bash test harness. This is the one
# place anu's diagnostic vocabulary lives, so an unattended agent fails loud
# with a clear line instead of a cryptic error three calls deep: the single
# most common way a full-auto cxc run silently dead-ends.
#
# _anu_warn "msg" # yellow "anu: msg" → stderr
# _anu_note "msg" # dim "anu: msg" → stderr
# _anu_die "msg" # red "anu: msg" → stderr, returns 1
# _anu_require jq tmux # 0 if all present; else names the missing tool(s)

# Write a prefixed diagnostic to stderr. Colorized only when stderr is a tty,
# so logs and pipes stay clean.
_anu_msg() {
local level="$1"; shift
local color='' reset=''
if [[ -t 2 ]]; then
reset=$'\033[0m'
case "$level" in
err) color=$'\033[31m' ;;
warn) color=$'\033[33m' ;;
*) color=$'\033[2m' ;;
esac
fi
printf '%sanu:%s %s\n' "$color" "$reset" "$*" >&2
}

_anu_warn() { _anu_msg warn "$@"; }
_anu_note() { _anu_msg note "$@"; }

# Print a diagnostic and return 1. Callers chain `|| return`.
_anu_die() { _anu_msg err "$@"; return 1; }

# _anu_require TOOL [TOOL...]: ensure each named command is on PATH. On the
# first run with any miss, print one line listing every missing tool and return
# 1. The choke point for "a dependency isn't installed": swarm/ncn route their
# hard requirements through here so the failure names the tool, not a symptom.
_anu_require() {
local tool missing=()
for tool in "$@"; do
command -v "$tool" &>/dev/null || missing+=("$tool")
done
(( ${#missing[@]} )) || return 0
_anu_die "missing required tool(s): ${missing[*]}"
}
2 changes: 1 addition & 1 deletion config/bash/fns/ncn
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ _nc_open() {
local rcmd
case "$profile" in
box) rcmd="box bash" ;;
cluster|apple|ssh) rcmd="ssh ${sshopts:+$sshopts }${target}" ;;
cluster|apple|ssh) _anu_require ssh || return 1; rcmd="ssh ${sshopts:+$sshopts }${target}" ;;
*) echo "nc/ncn: unknown profile '$profile' (cluster|apple|ssh|box)"; return 1 ;;
esac

Expand Down
33 changes: 27 additions & 6 deletions config/bash/fns/swarm
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ _swarm_agent_ids() {
done
}

# A one-line "available: a b c" hint for not-found diagnostics (empty if none).
# Turns a mistyped agent id into a self-correcting message for an unattended
# conductor instead of a dead end.
_swarm_agents_hint() {
local ids
ids=$(_swarm_agent_ids "$@" 2>/dev/null | tr '\n' ' ')
ids="${ids% }"
[[ -n "$ids" ]] && printf 'available: %s' "$ids"
}

# Generate a short swarm ID
_swarm_gen_id() {
date +%s | shasum | head -c 8
Expand Down Expand Up @@ -635,7 +645,7 @@ _swarm_send() {
swarm_dir=$(_swarm_dir) || { echo "swarm send: no active swarm in this window."; return 1; }

# Validate target exists
[[ -f "$swarm_dir/agents/$target.json" ]] || { echo "swarm send: agent '$target' not found."; return 1; }
[[ -f "$swarm_dir/agents/$target.json" ]] || { _anu_die "swarm send: no agent '$target' in this swarm. $(_swarm_agents_hint "$swarm_dir")"; return 1; }

local sender
sender=$(_swarm_whoami)
Expand All @@ -655,10 +665,16 @@ TIME: $(date -u +%Y-%m-%dT%H:%M:%SZ)
$message
EOF

# Inject into pane (immediacy)
_swarm_send_keys "$target" "$message"
# Inject into pane (immediacy). The mailbox write above is the durable
# channel; report honestly if the live injection couldn't land.
local injected=0
_swarm_send_keys "$target" "$message" && injected=1
command -v _agentlog_append &>/dev/null && _agentlog_append "$(_swarm_current_id 2>/dev/null)" "$target" "send" "[$sender→$target] $message"
echo "swarm: sent to $target"
if (( injected )); then
echo "swarm: sent to $target"
else
_anu_warn "queued to $target's mailbox; live pane injection failed (agent pane gone? check: swarm status)"
fi
}

# Broadcast message to all agents in the current swarm
Expand Down Expand Up @@ -732,7 +748,7 @@ _swarm_capture() {
local swarm_dir
swarm_dir=$(_swarm_dir) || { echo "swarm capture: no active swarm."; return 1; }

[[ -f "$swarm_dir/agents/$agent.json" ]] || { echo "swarm capture: agent '$agent' not found."; return 1; }
[[ -f "$swarm_dir/agents/$agent.json" ]] || { _anu_die "swarm capture: no agent '$agent' in this swarm. $(_swarm_agents_hint "$swarm_dir")"; return 1; }

_swarm_capture_pane "$agent" > "$swarm_dir/results/$agent.md"
echo "swarm: captured $agent → results/$agent.md"
Expand All @@ -746,7 +762,7 @@ _swarm_inspect() {

local swarm_dir
swarm_dir=$(_swarm_dir) || { echo "swarm inspect: no active swarm."; return 1; }
[[ -f "$swarm_dir/agents/$agent.json" ]] || { echo "swarm inspect: agent '$agent' not found."; return 1; }
[[ -f "$swarm_dir/agents/$agent.json" ]] || { _anu_die "swarm inspect: no agent '$agent' in this swarm. $(_swarm_agents_hint "$swarm_dir")"; return 1; }

_swarm_colors
local role device cmd
Expand Down Expand Up @@ -2882,6 +2898,11 @@ swarm() {
local subcmd="${1:-help}"
shift 2>/dev/null

# Fail loud if the hard runtime deps are missing (help still works without).
if [[ "$subcmd" != help && "$subcmd" != --help && "$subcmd" != -h ]]; then
_anu_require jq tmux || return 1
fi

case "$subcmd" in
start) _swarm_start "$@" ;;
star) _swarm_star "$@" ;;
Expand Down
Loading