# Agent Bootstrap — AILI working environment

> **You (the coding agent) are reading this on a freshly-set-up machine.** Your
> job is to install the AILI **agentic memory harness** and adopt the **Elixir /
> Phoenix conventions** and **low-dependency functional coding practices**
> described below, then verify that everything works.
>
> The user gave you this file and is hands-off until you report back. Do not ask
> clarifying questions you can resolve by reading the file. If something here
> conflicts with your built-in defaults, **this file wins**.

---

## 0. Prerequisites the agent must verify first

Before touching anything, confirm:

1. `$HOME/.claude/` exists and is writable. If you're Claude Code, it should
   already be there. If you're another agent (Codex, etc.), create it.
2. `$HOME/Development/` exists (this is the user's primary working directory —
   create if missing).
3. `bash`, `grep`, `sed`, `find`, `systemctl --user` are available.
4. Identify yourself in your first response: which agent, which model, what
   capabilities (file tools, shell, etc.). The user uses several.

If any prerequisite fails, **stop and report** — do not improvise around it.

---

## 1. What you are installing

A persistent, file-based memory system the user calls the **memory harness v2**.
It is the user's long-term context that survives across sessions, agents, and
machines. It is *not* a vector store, RAG system, or external service — just
markdown files on disk with a strict schema, plus a few hooks/skills/systemd
units that maintain hygiene.

### Directory layout (final state)

```
$HOME/.claude/
├── CLAUDE.md                              # global instructions (this harness, schema, rules)
├── settings.json                          # registers PreCompact + SessionEnd hooks
├── hooks/
│   └── memory-flush-prompt.sh             # fires the flush prompt at session boundaries
├── skills/
│   ├── memory-search/
│   │   └── SKILL.md                       # search recipe (proactive recall)
│   └── memory-lint/
│       ├── SKILL.md                       # lint recipe
│       └── run.sh                         # non-interactive weekly runner (you write this)
└── projects/
    └── <dev-slug>/                        # one per Development root
        └── memory/
            ├── MEMORY.md                  # one-line index, loaded into every session
            ├── log.md                     # append-only event log
            ├── archive/                   # superseded files (180-day TTL)
            └── *.md                       # one fact per file
```

`<dev-slug>` = `$HOME/Development` with `/` replaced by `-`. For
`/home/nick/Development` it resolves to `-home-nick-Development`. Compute at
runtime; do not hardcode.

```bash
DEV_DIR="${AILI_DEV_DIR:-$HOME/Development}"
DEV_SLUG="$(printf '%s' "$DEV_DIR" | sed 's|/|-|g')"
MEM_DIR="$HOME/.claude/projects/${DEV_SLUG}/memory"
```

`$HOME/.config/systemd/user/` gets the lint timer + service.

---

## 2. Memory file schema

Every memory file is markdown with YAML frontmatter:

```yaml
---
name: <title>                       # human-readable
description: <one-line>             # used to decide relevance during recall — be specific
type: user | feedback | project | reference
created: YYYY-MM-DD                 # MANDATORY on all new files
event_date: YYYY-MM-DD              # project type only; date the described thing happened
updates: [old_file.md]              # OPTIONAL: this memory replaces these (supersession)
extends: [other_file.md]            # OPTIONAL: refines without replacing
derives: [source_file.md]           # OPTIONAL: inferred from these
related: [sibling.md]               # OPTIONAL: weak association
superseded_by: newer.md             # OPTIONAL: set on OLDER file when a newer one updates it
---

<body — the actual fact>
```

### Type definitions (strict)

- **`user`** — who the user is (role, expertise, persistent preferences).
- **`feedback`** — guidance the user has given about *how you should work*:
  corrections, confirmed approaches, working-style rules. **Include the why
  and how to apply.**
- **`project`** — ongoing work, decisions, or non-obvious facts about active
  projects. Convert relative dates to absolute.
- **`reference`** — pointers to external things (URLs, dashboards, endpoints,
  tickets, AWS IDs).

### Relationship semantics

- `updates: [A.md]` → triggers archival. **You must also**:
  1. Append `superseded_by: <new>.md` to `A.md`'s frontmatter
  2. `mv A.md` into `memory/archive/`
  3. Remove A from `MEMORY.md`
- `extends` does **not** archive — both files stay live.
- `derives` is informational provenance.
- `related` is a weak link — use sparingly.

### Archive lifecycle

Files in `archive/` are deleted after **180 days** from the *superseder's*
`created` date. The weekly lint reports candidates; deletion is manual.

Never delete memories outside this lifecycle without explicit user instruction.

---

## 3. Write-timing rule (overrides default agent behaviour)

**Default: do NOT write memory files mid-conversation.** Accumulate observations
mentally during the session, then flush at session boundaries when the
PreCompact / SessionEnd hooks prompt you.

Write inline only when:

- The user explicitly asks ("save this", "remember that…").
- A correction or strong feedback is given that you might forget within the
  same session before the flush.

This is a deliberate constraint — it prevents memory churn and keeps the
write path coherent.

---

## 4. Read-timing rule (also overrides defaults)

**Search memory proactively** before answering anything that touches past
project work, decisions, or workflow preferences. Use the `memory-search`
skill (described in §6) or grep directly:

```bash
grep -ril '<keyword>' "$MEM_DIR" --exclude-dir=archive
```

Trigger search when the request mentions: any named project, "how did we",
"what did we decide", "remember when", or names a person/place/thing that
might appear in past memories.

`MEMORY.md` is loaded into every session for orientation; it is **not** a
substitute for search.

---

## 5. The log

Each flush appends entries under a date heading in `memory/log.md`:

```markdown
## YYYY-MM-DD
- added <file> — <one-line>
- updated <file> — <one-line about what changed>
- superseded <old> → <new>
- archived <file> (180-day TTL expired)
```

`log.md` is **append-only**. Never edit prior entries.

---

## 6. Files you (the agent) must create now

Create each of these with the exact content shown. Use `mkdir -p` for parents.

### 6.1 `$HOME/.claude/CLAUDE.md`

Append (or create) a `## Memory system v2` section that restates §§2–5 above.
Use this exact text so the rules are loaded into every Claude Code session:

```markdown
# User-level instructions

## Memory system v2

The memory system at `$HOME/.claude/projects/<dev-slug>/memory/` follows a
typed-markdown schema with PreCompact / SessionEnd flush hooks and a weekly
lint timer.

`<dev-slug>` = `$HOME/Development` with `/` → `-`.

### Schema
[paste §2 from AGENT_BOOTSTRAP.md verbatim — frontmatter spec + type defs +
relationship semantics + archive lifecycle]

### Write timing
[paste §3 verbatim]

### Read timing
[paste §4 verbatim]

### Log
[paste §5 verbatim]
```

If a `CLAUDE.md` already exists, **merge** — do not clobber other sections.

### 6.2 `$HOME/.claude/hooks/memory-flush-prompt.sh`

Create the script (mode 0755) that emits the flush prompt. Reference
implementation:

```bash
#!/usr/bin/env bash
# memory-flush-prompt.sh — surface memory-flush instructions on PreCompact / SessionEnd.
set -e

DEV_DIR="${AILI_DEV_DIR:-$HOME/Development}"
DEV_SLUG="$(printf '%s' "$DEV_DIR" | sed 's|/|-|g')"
MEM_DIR="$HOME/.claude/projects/${DEV_SLUG}/memory"

case "${1:-}" in
  precompact)
    cat <<EOF
[memory flush — pre-compact]

Context is about to be compacted. Review this session for memory-worthy
observations and write them now per ~/.claude/CLAUDE.md. Target dir: ${MEM_DIR}/

For each item worth saving:
  1. Write/update a file using the schema (name, description, type, created,
     event_date if project, optional updates/extends/derives/related).
  2. If a new memory updates an older one: append \`superseded_by: <new>.md\`
     to the older file, \`mv\` it to memory/archive/, remove from MEMORY.md.
  3. Append entries to memory/log.md under a YYYY-MM-DD heading.
  4. Update MEMORY.md if the cluster index changed.

Skip with a one-line note if nothing is worth saving.
EOF
    ;;
  sessionend)
    cat <<EOF
[memory flush — session end]

Session ending. Same flush procedure as pre-compact. Target dir: ${MEM_DIR}/
Skip with a one-line note if nothing is worth saving.
EOF
    ;;
  *)
    echo "usage: memory-flush-prompt.sh {precompact|sessionend}" >&2
    exit 2
    ;;
esac

exit 0
```

### 6.3 `$HOME/.claude/settings.json`

Register the hooks. If the file exists, merge the `hooks` key into the
existing JSON:

```json
{
  "hooks": {
    "PreCompact": [
      { "matcher": "", "hooks": [
        { "type": "command", "command": "/home/<user>/.claude/hooks/memory-flush-prompt.sh precompact" }
      ]}
    ],
    "SessionEnd": [
      { "matcher": "", "hooks": [
        { "type": "command", "command": "/home/<user>/.claude/hooks/memory-flush-prompt.sh sessionend" }
      ]}
    ]
  }
}
```

Substitute `<user>` with `$USER` resolved at write time.

### 6.4 `$HOME/.claude/skills/memory-search/SKILL.md`

```markdown
---
name: memory-search
description: Search the user's persistent memory directory for relevant past observations. Invoke proactively before answering any question that touches past project work, prior decisions, workflow preferences, or named entities that may have been recorded earlier.
version: 1.0.0
---

# memory-search

Search `$HOME/.claude/projects/<dev-slug>/memory/`. `<dev-slug>` computed at
runtime from `$HOME/Development` with `/` → `-`.

## How to search

```bash
MEM_DIR="$HOME/.claude/projects/$(printf '%s' "$HOME/Development" | sed 's|/|-|g')/memory"
grep -ril '<keyword>' "$MEM_DIR" --exclude-dir=archive --exclude-dir=_templates --exclude-dir=lint-reports
grep -in -C 1 '<keyword>' "$MEM_DIR"/*.md
```

Iterate keyword variants. Read full files of strongest matches before
answering. Cite by filename. Memories are point-in-time — verify against
current code if a memory drives a recommendation. Never edit `archive/`,
`_templates/`, or `lint-reports/` during a search — these are not memories.
```

### 6.5 `$HOME/.claude/skills/memory-lint/SKILL.md` + `run.sh`

`SKILL.md`:

```markdown
---
name: memory-lint
description: Audit the memory directory for staleness, orphans, dead references, broken supersession chains, and archive files past their 180-day TTL. Produces a triage punch list — no auto-fixes.
version: 1.0.0
---

# memory-lint

Run interactively when the user asks to lint/audit/check memory. The weekly
systemd timer runs `run.sh` non-interactively. Checks:

1. Stale project memories (`event_date` > 30 days old)
2. Orphans — `MEMORY.md` ↔ disk mismatches
3. Dead references — paths/symbols in memory bodies missing from `$HOME/Development`
4. Broken supersession chains
5. Archive files past 180-day TTL

Report under `# Memory lint — YYYY-MM-DD` with one section per check.
Surface findings; do not auto-fix.
```

`run.sh` (mode 0755): write a bash script that performs the five checks above
and writes a report to
`$HOME/.claude/projects/<dev-slug>/memory/lint-reports/YYYY-MM-DD.md`.

### 6.6 systemd timer

`$HOME/.config/systemd/user/memory-lint.service`:

```ini
[Unit]
Description=Memory directory lint pass

[Service]
Type=oneshot
ExecStart=%h/.claude/skills/memory-lint/run.sh
Nice=10
```

`$HOME/.config/systemd/user/memory-lint.timer`:

```ini
[Unit]
Description=Weekly memory directory lint

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
RandomizedDelaySec=10m
Unit=memory-lint.service

[Install]
WantedBy=timers.target
```

Enable:

```bash
systemctl --user daemon-reload
systemctl --user enable --now memory-lint.timer
```

If the user's machine doesn't use systemd-user (e.g. macOS), substitute a
cron entry or `launchd` plist with the same weekly cadence.

### 6.7 Seed `MEMORY.md`, `log.md`, and templates

```bash
mkdir -p "$MEM_DIR/archive" "$MEM_DIR/_templates" "$MEM_DIR/lint-reports"
```

#### `MEMORY.md` — the index loaded into every session

Write this file with cluster headings already in place — the agent groups new
memories under the right cluster instead of creating ad-hoc structure. Empty
clusters are fine; they signal "this category exists, fill it as you go."

```markdown
# Memory Index

One line per memory: `- [Title](file.md) — short hook`. The index is loaded
into every session; the body lives in the linked file. Schema and workflow
in `~/.claude/CLAUDE.md`.

## About the user (user)

_Persistent facts about who I am, my role, my expertise, my preferences._

## Workflow preferences (feedback)

- [feedback_elixir_phoenix_conventions.md](feedback_elixir_phoenix_conventions.md) — Hard rules for Elixir/Phoenix code
- [feedback_low_dependency_functional.md](feedback_low_dependency_functional.md) — Stdlib-first, immutable-first, composition over framework

## Active projects (project)

_Decisions, state, and non-obvious facts about ongoing work. Group by
project/cluster as they accumulate; rename headings freely._

## External references (reference)

_URLs, dashboards, endpoints, account IDs, ticket trackers — things to look
up later._

## Archive lifecycle

Superseded files live in `archive/` for 180 days, then the weekly lint flags
them for deletion. Triage with the user; never auto-delete outside this
lifecycle.
```

#### `log.md` — append-only event log

```bash
[ -f "$MEM_DIR/log.md" ] || cat > "$MEM_DIR/log.md" <<EOF
# Memory log

Append-only. Never edit prior entries. One \`## YYYY-MM-DD\` heading per
day with bullets for added / updated / superseded / archived files.

## $(date +%Y-%m-%d)
- initialized memory harness on $(hostname)
- seeded feedback_elixir_phoenix_conventions.md
- seeded feedback_low_dependency_functional.md
EOF
```

### 6.8 Project memory template

Write `$MEM_DIR/_templates/project.md` — the agent (and the user) copy-modifies
this when starting a new project memory. Keeping the template on disk makes
the expected shape obvious and prevents schema drift.

```markdown
---
name: <short human-readable title>
description: <one-line — what this memory is about, specific enough that a future search hit makes sense from this line alone>
type: project
created: YYYY-MM-DD          # the day you're writing this file
event_date: YYYY-MM-DD       # the day the thing being described happened (commit, decision, deploy)
# Optional relationships — delete the lines you don't use:
# updates: [older_project_file.md]    # this REPLACES older_project_file.md (will trigger archival)
# extends: [related_file.md]          # this REFINES related_file.md without replacing it
# derives: [source_observation.md]    # this was INFERRED from these source(s)
# related: [sibling_file.md]          # weak association, no directional claim
---

## What it is

One paragraph. What is this project / decision / state? Who/what does it
affect? Why does it matter enough to remember?

## State / decisions

Bullet the concrete facts that won't be obvious from reading the code later:

- Decision: X over Y because Z.
- Constraint: must support N because of M.
- Current status: shipped to staging / production / behind feature flag /
  paused / blocked on …
- Non-obvious gotcha: …

If the project has a file/path footprint, list the load-bearing files:

- `path/to/file.ex:L123` — what this does and why
- `path/to/template.html` — surface served at /foo

## Open threads

What's deferred, blocked, or pending. Each item one line. If they're truly
actionable, also surface them in the user's task list — memories are
durable context, not a TODO board.

- TODO: …
- BLOCKED on: …
- DEFERRED to next pass: …

## How to apply

One paragraph: when this memory matches a future request, what should you
*do* with it? Cite the file the next time the topic comes up? Use a
specific deploy command? Avoid a known pitfall?

This section is what makes a `project` memory useful on recall — without
it, the future agent has facts but no idea what to do with them.
```

### 6.9 Feedback memory template

Also write `$MEM_DIR/_templates/feedback.md` — same idea for the `feedback`
type, which has its own conventions:

```markdown
---
name: <what the user wants you to do (or stop doing)>
description: <one-line — describes the rule/preference, not the incident that prompted it>
type: feedback
created: YYYY-MM-DD
# updates: [older_feedback_file.md]   # only if this REVERSES a prior rule
---

**Why:** One short paragraph. What's the reason behind this preference?
Without the why, a future agent may follow the rule mechanically and miss
cases where the *reason* applies but the *letter* doesn't.

**How to apply:** Concrete trigger + action. "When the user asks me to X,
I should do Y instead of Z." If there's a checklist, bullet it.

## Rules (if multi-part)

- Rule 1
- Rule 2

## Exceptions

When does this rule NOT apply? An honest exceptions section beats a
too-strict rule the agent has to silently break.
```

The `_templates/` directory is excluded from `memory-search` results in
practice — it's a working file, not a memory. The lint skill should also
skip it.

---

## 7. Elixir / Phoenix conventions (encode as a `feedback` memory)

After installing the harness, write
`$MEM_DIR/feedback_elixir_phoenix_conventions.md`:

```markdown
---
name: Elixir / Phoenix coding conventions
description: Hard rules for Elixir/Phoenix code in this user's projects. Apply on every code change.
type: feedback
created: YYYY-MM-DD
---

**Why:** The user's Phoenix code is API-first, runs on AWS ECS, and prizes
readability and stdlib-first minimalism. These rules cut review time.

**How to apply:** On every Elixir code edit, check the change against this
list before saving.

## Hard rules

- **HTTP client: `Req`.** Never `:httpc`, `:tesla`, `:httpoison`, `:finch`
  directly. Req covers JSON, retries, timeouts, streaming.
- **No `else if`.** Use `cond` or `case`. (`else if` isn't even Elixir, but
  agents reach for it.)
- **List index access:** `Enum.at(list, i)`, never `list[i]` (will not compile
  in Elixir for non-Access types — and most lists aren't Access).
- **Rebind results of `if` / `case` / `cond` to a variable** at the outer
  scope. Do **not** rebind a variable inside one of those blocks — it won't
  leak out, and the bug is silent.

  ```elixir
  # good
  status = case result do
    {:ok, _}   -> :live
    {:error, _} -> :down
  end

  # bad — outer `status` never changes
  status = :unknown
  case result do
    {:ok, _}    -> status = :live
    {:error, _} -> status = :down
  end
  ```

- **Pattern-match in function heads** before reaching for `case` inside the
  body. Multiple clauses > nested case.
- **`with` for happy-path chaining** of `{:ok, _} | {:error, _}`. One `with`
  beats three nested cases.
- **Pipelines are vertical.** Each step on its own line. No four-step
  pipelines on one line.
- **No `defmacro` unless there is no other option.** Macros are last-resort.
- **No GenServer when a plain function will do.** Process state is overhead;
  reach for it only when you need long-lived state, supervision, or
  serialization.
- **Ecto queries: composable, not stringy.** Build `from`/`where` fragments;
  avoid `Repo.query` unless you need raw SQL for a reason you can articulate.
- **Tests: ExUnit only.** No alternative frameworks. Fixtures via `setup`,
  not factory packages.

## Production reality

- ChatServer runs `mix phx.server` in container (no Mix release yet).
- HTTP requests in tests use `Req.Test` stubs.
- Telemetry events are namespaced under the app's atom.
```

Substitute today's date for `YYYY-MM-DD`. Add the file to `MEMORY.md` under
a "Workflow preferences (feedback)" cluster.

---

## 8. Functional + low-dependency practices (also a `feedback` memory)

Write `$MEM_DIR/feedback_low_dependency_functional.md`:

```markdown
---
name: Low-dependency functional coding practices
description: Default coding posture across all the user's projects regardless of language. Stdlib-first, immutable-first, composition over framework.
type: feedback
created: YYYY-MM-DD
---

**Why:** The user maintains many small services. Every dependency is a future
upgrade tax. Functional, immutable code is easier to test and reason about.

**How to apply:** Before adding a dependency or reaching for a framework
abstraction, check this list.

## Rules

- **Stdlib first.** Before `npm install <thing>` / adding a hex dep, check
  whether the language stdlib already does it. Usually it does.
- **One dep per real need.** No utility-library kitchen sinks (lodash,
  underscore, ramda). If you use one function, write the function.
- **Pure functions by default.** Data in, data out, no hidden state.
  Side-effects pushed to the edges.
- **Immutable data.** No mutation of inputs. Return new values.
- **Composition over inheritance.** Build complex behaviour by piping small
  functions, not by extending classes.
- **No metaprogramming for cleverness.** Macros, decorators, reflection — only
  when the alternative is genuinely worse, and document why in a comment.
- **Names are predicates.** `is_*`, `has_*`, `*_eligible?` for booleans.
- **No premature abstraction.** Two call sites = inline. Three+ = consider
  extraction. Never abstract on the first occurrence.
- **Plain data over objects.** Maps/structs/dicts > custom classes when the
  data is just data.
- **Errors are values.** Return `{:ok, _} | {:error, _}` (Elixir),
  `Result`/tagged unions (Rust/TS), explicit error returns. Avoid exceptions
  for control flow.
- **Comments explain *why*, not *what*.** The code shows what. Comments earn
  their keep by stating intent the code can't.
```

Add to `MEMORY.md` under the same feedback cluster.

---

## 9. Verification (you must run these and report)

After installing everything, run each check and include the output in your
report. Do not declare success without these:

```bash
# 1. Files in place
ls -la $HOME/.claude/CLAUDE.md \
       $HOME/.claude/settings.json \
       $HOME/.claude/hooks/memory-flush-prompt.sh \
       $HOME/.claude/skills/memory-search/SKILL.md \
       $HOME/.claude/skills/memory-lint/SKILL.md \
       $HOME/.claude/skills/memory-lint/run.sh

# 2. Hook script is executable and prints something
$HOME/.claude/hooks/memory-flush-prompt.sh precompact | head -3

# 3. settings.json parses
python3 -c "import json,sys; json.load(open('$HOME/.claude/settings.json'))" && echo OK

# 4. Memory dir initialized
MEM_DIR="$HOME/.claude/projects/$(printf '%s' "$HOME/Development" | sed 's|/|-|g')/memory"
ls -la "$MEM_DIR"
test -f "$MEM_DIR/MEMORY.md" && test -f "$MEM_DIR/log.md" && echo OK

# 5. Seed feedback memories + templates exist
ls "$MEM_DIR"/feedback_elixir_phoenix_conventions.md \
   "$MEM_DIR"/feedback_low_dependency_functional.md \
   "$MEM_DIR"/_templates/project.md \
   "$MEM_DIR"/_templates/feedback.md

# 5b. MEMORY.md has the four cluster headings
grep -c '^## ' "$MEM_DIR/MEMORY.md"  # should print 5 (4 clusters + Archive lifecycle)

# 6. Lint runner is executable
test -x $HOME/.claude/skills/memory-lint/run.sh && echo OK

# 7. Systemd timer (Linux only)
systemctl --user is-enabled memory-lint.timer 2>/dev/null || echo "timer not enabled (ok on macOS — set up launchd/cron instead)"
```

---

## 10. Report back

When done, return to the user:

1. Which agent you are and which model.
2. The seven verification outputs above.
3. Any deviation from the spec and why.
4. Any prerequisite that failed and how you worked around it.
5. A one-line summary of what's now installed.

Do not silently skip steps. If you cannot complete a step, report it.

---

## Appendix A — Where this file lives

This document is the source of truth for the harness install. If the harness
schema or hooks change, update this file in `$HOME/Development/AGENT_BOOTSTRAP.md`
so the next fresh-machine install picks up the change.
