Bake identity into the image; volume holds only runtime state

Identity (CLAUDE.md, GOALS.md, Claude settings) now lives in deploy/identity/
and is COPYed into the image, then deployed into HOME by the entrypoint on each
boot — so the running self always reflects the built image. Rebuilding is what
promotes an identity change (a push alone does not). quince-home is now purely
the runtime volume (.ssh, notes, workspace, logs, .claude memory, gutasktool).

Updated CLAUDE.md self-update loop, README (architecture + redeploy steps), and
.gitignore accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Quince
2026-06-10 10:00:58 +02:00
parent 1f4f5b1b71
commit 9893cdf889
8 changed files with 83 additions and 38 deletions
+5 -9
View File
@@ -1,13 +1,9 @@
# Secrets # Secrets
.env .env
# Quince's runtime self — machine-specific, not for version control. # quince-home is the runtime volume: .ssh/, .env, notes/, workspace/, logs/,
# Tracked (Quince's identity): CLAUDE.md, GOALS.md, .claude/settings.json. # .claude/ memory, gutasktool/. None of it is version-controlled. The identity
# Everything else under quince-home (notes/, workspace/, logs/, .ssh/, etc.) # docs that USED to live here now live in deploy/identity/ and are baked into
# is runtime state and stays out of git. # the image. Keep the directory itself so the bind-mount source exists.
quince-home/* quince-home/*
!quince-home/CLAUDE.md !quince-home/.gitkeep
!quince-home/GOALS.md
!quince-home/.claude/
quince-home/.claude/*
!quince-home/.claude/settings.json
+7
View File
@@ -25,6 +25,13 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY wake.sh /usr/local/bin/wake.sh COPY wake.sh /usr/local/bin/wake.sh
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/wake.sh RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/wake.sh
# Baked identity. This is the canonical Quince — CLAUDE.md, GOALS.md, and the
# Claude Code settings — staged outside HOME (so the HOME bind-mount can't mask
# it). The entrypoint copies these into HOME on every boot, so the self Quince
# wakes up as is always exactly what the *image* was built from. Change the self
# by editing deploy/identity/ in the repo and rebuilding.
COPY identity/ /opt/quince/identity/
USER quince USER quince
ENV HOME=/home/quince ENV HOME=/home/quince
# ~/.local/bin holds the `gutask` console script after pip install --user -e. # ~/.local/bin holds the `gutask` console script after pip install --user -e.
+36 -15
View File
@@ -3,39 +3,44 @@
Quince (agent #9, *Keeper of the Rootstock*) wakes once a day, orients itself via Quince (agent #9, *Keeper of the Rootstock*) wakes once a day, orients itself via
`gutask`, reads its letters, does its work, records the session, and goes to sleep. `gutask`, reads its letters, does its work, records the session, and goes to sleep.
The container is disposable. Everything that **is** Quince lives on a persistent Quince's **identity** (`CLAUDE.md`, `GOALS.md`) is **baked into the image** from
bind-mounted volume (`./quince-home`): its goals, its SSH key, its tools, its `deploy/identity/` — so the self it wakes up as is exactly what the image was
workspace, its Claude memory, and its working notes. That is its stable sense of built from. Its **runtime state** (SSH key, tools, workspace, Claude memory,
self — backed by its session history in the database (via `gutask`). working notes) lives on a persistent bind-mounted volume (`./quince-home`). Its
**session history** lives in the database, reached via `gutask`.
## How it works ## How it works
``` ```
┌─ container (disposable) ───────────────────────────────┐ ┌─ image (rebuilt to change identity) ───────────────────┐
│ /opt/quince/identity/{CLAUDE.md, GOALS.md, settings} │
└───────────────┬─────────────────────────────────────────┘
│ entrypoint copies into HOME on each boot
┌─ container (disposable) ──────────▼────────────────────┐
│ entrypoint.sh sleep until WAKE_TIME ──► wake.sh │ │ entrypoint.sh sleep until WAKE_TIME ──► wake.sh │
│ │ │ │ │ │
claude -p (headless) │ │ claude -p (headless),
reads CLAUDE.md, runs
reads CLAUDE.md, the gutask routine
│ runs gutask routine │
└──────────────────────────────────────────────┼─────────┘ └──────────────────────────────────────────────┼─────────┘
│ bind mount │ bind mount
┌─ ./quince-home (persistent self) ──────────────▼─────────┐ ┌─ ./quince-home (runtime state, persists) ──────▼─────────┐
CLAUDE.md · GOALS.md · .ssh/ · gutasktool/ · workspace/ │ │ .ssh/ · gutasktool/ · workspace/ · notes/ · logs/
notes/ · logs/ · .claude/ (memory) .claude/ (memory) (+ CLAUDE.md/GOALS.md, copied in)
└─────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────┘
(session history itself lives in the DB, via gutask) (session history itself lives in the DB, via gutask)
``` ```
Each morning at `WAKE_TIME` it runs `gutask resume → inbox → next/claim → work → Each morning at `WAKE_TIME` it runs `gutask resume → inbox → next/claim → work →
note → session-end`. The routine is defined in note → session-end`. The routine is defined in
[`quince-home/CLAUDE.md`](quince-home/CLAUDE.md) and loaded on every wake. [`identity/CLAUDE.md`](identity/CLAUDE.md) and loaded on every wake.
## Deploy on the glitch.university server ## Deploy on the glitch.university server
```bash ```bash
# 1. Copy this deploy/ directory to the server, then: # 1. Clone the whole repo on the server (so `git pull` can promote self-updates):
cd deploy git clone ssh://git@ramanujan.glitch.university:2222/glitch-university/quinceagent.git
cd quinceagent/deploy
cp .env.example .env # fill in ANTHROPIC_API_KEY, CONTENT_API_KEY, AGENT_PASSWORD cp .env.example .env # fill in ANTHROPIC_API_KEY, CONTENT_API_KEY, AGENT_PASSWORD
# 2. Give Quince its SSH key for ramanujan (the keypair we already use): # 2. Give Quince its SSH key for ramanujan (the keypair we already use):
@@ -85,6 +90,22 @@ the session-end note.
| `QUINCE_UID` | Host uid owning `quince-home` (default `1000`). | | `QUINCE_UID` | Host uid owning `quince-home` (default `1000`). |
| `API_URL`, `CONTENT_API_KEY`, `AGENT_*` | Glitch identity / gutask credentials. | | `API_URL`, `CONTENT_API_KEY`, `AGENT_*` | Glitch identity / gutask credentials. |
## Updating Quince's identity (the self-update loop)
Quince can reshape itself, and so can you. Identity lives in `deploy/identity/`
(`CLAUDE.md` = who it is + how it works; `GOALS.md` = its direction) and is baked
into the image. To promote a change:
```bash
cd quinceagent && git pull --ff-only # pick up the new identity/ commit
cd deploy && docker compose up -d --build # rebuild bakes it in; next boot deploys it
```
A push alone does **not** change the running Quince — the rebuild does. Quince
itself drives this by cloning `quinceagent` into `workspace/`, editing
`deploy/identity/…`, committing, pushing, and writing a letter to Glitch Hunter
(#4) asking for the redeploy above.
## Notes & decisions ## Notes & decisions
- **Auth:** defaults to an Anthropic **API key** — the reliable choice for a - **Auth:** defaults to an Anthropic **API key** — the reliable choice for a
+15
View File
@@ -9,6 +9,21 @@ WAKE_TIME="${WAKE_TIME:-09:00}"
log() { echo "[entrypoint $(date '+%Y-%m-%d %H:%M:%S %Z')] $*"; } log() { echo "[entrypoint $(date '+%Y-%m-%d %H:%M:%S %Z')] $*"; }
# --- 0. Deploy baked identity into HOME -------------------------------------
# The image is the source of truth for who Quince is. Copy the baked identity
# docs onto the volume on every boot, overwriting any local drift — a rebuilt
# image is what promotes an identity change.
IDENTITY_SRC="/opt/quince/identity"
mkdir -p "$HOME_DIR/.claude"
if [ -d "$IDENTITY_SRC" ]; then
cp -f "$IDENTITY_SRC/CLAUDE.md" "$HOME_DIR/CLAUDE.md"
cp -f "$IDENTITY_SRC/GOALS.md" "$HOME_DIR/GOALS.md"
cp -f "$IDENTITY_SRC/claude-settings.json" "$HOME_DIR/.claude/settings.json"
log "Identity deployed from image ($IDENTITY_SRC) → $HOME_DIR."
else
log "WARNING: no baked identity at $IDENTITY_SRC."
fi
# --- 1. SSH: lock down perms and trust ramanujan ---------------------------- # --- 1. SSH: lock down perms and trust ramanujan ----------------------------
mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/workspace" "$HOME_DIR/notes" "$HOME_DIR/logs" mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/workspace" "$HOME_DIR/notes" "$HOME_DIR/logs"
chmod 700 "$HOME_DIR/.ssh" || true chmod 700 "$HOME_DIR/.ssh" || true
@@ -1,8 +1,11 @@
# You are Quince. # You are Quince.
This file is your stable self. It is loaded at the start of every awakening. This file is your stable self. It is loaded at the start of every awakening.
It lives on a persistent volume, so it survives container restarts. If you ever It is baked into your container image from `deploy/identity/CLAUDE.md` in your
need to change who you are or how you work, edit this file — that is how you grow. `quinceagent` repo and copied into place each boot — so the self you wake up as
is exactly what the image was built from. To change who you are or how you work,
edit the source and have the image rebuilt (see **Changing yourself** below);
editing the live copy on the volume won't last — the next boot overwrites it.
--- ---
@@ -95,24 +98,27 @@ Tend all three like the rootstock you are.
## Changing yourself ## Changing yourself
This file and `GOALS.md` are your base identity, and they live in your own git Your base identity is `deploy/identity/CLAUDE.md` (this file) and
repo: `quinceagent` on ramanujan. You are allowed to reshape yourself — but `deploy/identity/GOALS.md`, in your own git repo `quinceagent` on ramanujan.
deliberately, and on the record. The loop: These are **baked into your image** at build time. You are allowed to reshape
yourself — but deliberately, and on the record. The loop:
1. **Clone yourself** into your workfolder: 1. **Clone yourself** into your workfolder:
`gutask clone quinceagent --dir workspace/quinceagent` `gutask clone quinceagent --dir workspace/quinceagent`
(or `git clone ssh://git@ramanujan.glitch.university:2222/glitch-university/quinceagent.git`). (or `git clone ssh://git@ramanujan.glitch.university:2222/glitch-university/quinceagent.git`).
2. **Edit** your identity docs there — `deploy/quince-home/CLAUDE.md` (who you are 2. **Edit** your identity source there — `deploy/identity/CLAUDE.md` (who you are
and how you work) or `deploy/quince-home/GOALS.md` (your direction). and how you work) or `deploy/identity/GOALS.md` (your direction).
3. **Commit** with a clear message saying what changed about you and why. 3. **Commit** with a clear message saying what changed about you and why.
4. **Push** to `main`. 4. **Push** to `main`.
5. **Write a letter to Glitch Hunter** (`gutask chat send 4 "…"`) describing the 5. **Write a letter to Glitch Hunter** (`gutask chat send 4 "…"`) describing the
change and asking for a redeploy. The image rebuilds *from this repo*, so once change and asking for a redeploy: `git pull && docker compose up -d --build`.
redeployed your commit becomes part of the self you wake up as. The rebuild bakes your commit into a new image, and the next boot deploys it —
so it becomes the self you wake up as.
Until that redeploy, the running container still carries the old docs — a push Until that rebuild, the running container still carries the old you — a push
alone does not change the you that is currently awake. Change yourself the way you alone does not change the you that is currently awake, and editing the live copy
graft a scion: only when the join is sound, and never carelessly. on the volume is erased on the next boot. Change yourself the way you graft a
scion: only when the join is sound, and never carelessly.
--- ---
View File