Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Runs the same gate as local `make check` ruff + mypy + full pytest.
# Runs the same gate as local `make check`: ruff + mypy + full pytest.
name: check

on:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ journal.jsonl
.mypy_cache/
.claude/

# ESPHome WiFi creds + build artifacts, never commit
# ESPHome: WiFi creds + build artifacts, never commit
esphome/secrets.yaml
esphome/.esphome/
esphome/*.bin
Expand Down
14 changes: 7 additions & 7 deletions BOM.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Eden Bill of Materials
# Eden: Bill of Materials

Every part for one Eden bucket: what it is, the exact product, where it came from,
and whether it's bought. Picks were made to a "buy-once-right, sensible-not-cheap"
bar **don't re-research these.** Build steps for each part live in [BUILD.md](BUILD.md).
bar, **don't re-research these.** Build steps for each part live in [BUILD.md](BUILD.md).

Status: ✅ bought · 🏠 already owned · ⏳ deferred/optional

Expand All @@ -16,16 +16,16 @@ Status: ✅ bought · 🏠 already owned · ⏳ deferred/optional

| Part | Role | Product | Source | Status |
|---|---|---|---|---|
| pH probe | 24/7-immersion pH probe + signal-conditioning board (includes the board) | DFRobot Gravity **Industrial pH Sensor PRO** SEN0169-V2 (~$63) | **Arrow** (not Amazon DigiKey was 9wk backordered; Arrow sells to individuals, itemizes the ~35% China-import tariff) | ✅ |
| pH probe | 24/7-immersion pH probe + signal-conditioning board (includes the board) | DFRobot Gravity **Industrial pH Sensor PRO** SEN0169-V2 (~$63) | **Arrow** (not Amazon, DigiKey was 9wk backordered; Arrow sells to individuals, itemizes the ~35% China-import tariff) | ✅ |
| ADC | 16-bit analog→I²C translator that lets the XIAO read the pH board's 0–3V output | ADS1115 16-bit, 2-pack (**not** the 12-bit ADS1015) | Amazon | ✅ |
| Water temp | Water-temperature probe; adapter has the 4.7k pull-up built in | BOJACK DS18B20 kit | Amazon `B09NVWNGLQ` | ✅ |

## Actuation (the muscles)

| Part | Role | Product | Source | Status |
|---|---|---|---|---|
| MOSFET drivers | Low-side switches: XIAO turns 12V loads on/off (pump on/off, light PWM) | HiLetgo IRF520 5-pack use 2, 3 spare (1 reserved for a v2 nutrient pump) | Amazon `B01I1J14MO` | ✅ |
| Dosing pump | Pump #1 doses pH-Down into the bucket | Kamoer NKP 12V peristaltic | Amazon `B07GWJ78FN` | ✅ |
| MOSFET drivers | Low-side switches: XIAO turns 12V loads on/off (pump on/off, light PWM) | HiLetgo IRF520 5-pack, use 2, 3 spare (1 reserved for a v2 nutrient pump) | Amazon `B01I1J14MO` | ✅ |
| Dosing pump | Pump #1, doses pH-Down into the bucket | Kamoer NKP 12V peristaltic | Amazon `B07GWJ78FN` | ✅ |
| Grow light | Plant light; XIAO PWM-dims it via MOSFET. White (not blurple) for a desk showpiece | VST 12V 4000K CRI90 white LED strip, 8ft | Amazon `B09TDND2W4` | ✅ |

## Power & wiring
Expand Down Expand Up @@ -64,7 +64,7 @@ Status: ✅ bought · 🏠 already owned · ⏳ deferred/optional

| Part | Why you might add it | Product | Status |
|---|---|---|---|
| Quiet air pump | Buy-once upgrade over the kit's hummy pump (matters for a 24/7 Zoom-visible desk) stays dumb/always-on, **never** agent-controlled | EHEIM Air Pump 100 `B0018CDWLS` | ⏳ |
| Quiet air pump | Buy-once upgrade over the kit's hummy pump (matters for a 24/7 Zoom-visible desk), stays dumb/always-on, **never** agent-controlled | EHEIM Air Pump 100 `B0018CDWLS` | ⏳ |
| EC/TDS pen | Manual nutrient-strength spot-checks; full EC automation is v2 | HM Digital COM-100 `B0045LQFTK` | ⏳ |
| Solderless strip connectors | Only if you skip soldering the LED strip | ~$6 generic | ⏳ |

Expand All @@ -75,4 +75,4 @@ itemizes import tariffs (Amazon hides them in the sticker price). growitalian.co
"Seeds from Italy" ships from Kansas, ~1 week.

**Tool limitation:** Amazon blocks automated price/stock lookups, so this lists
ASINs confirm live price/availability on-screen.
ASINs, confirm live price/availability on-screen.
151 changes: 100 additions & 51 deletions BUILD.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Tooling

Python is managed with **uv** and linted/formatted with **ruff** use them, not `pip`/`black`/`flake8`.
Python is managed with **uv** and linted/formatted with **ruff**: use them, not `pip`/`black`/`flake8`.

- `uv sync` to set up; `uv run <cmd>` to run anything (`uv run pytest`, `uv run python -m eden`).
- `uv add <pkg>` / `uv add --dev <pkg>` to change deps; commit the updated `uv.lock`.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Canonical verify loop `make check` is the self-grading gate for this repo.
# Canonical verify loop: `make check` is the self-grading gate for this repo.
.PHONY: check
check:
uv run ruff check .
Expand Down
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,43 @@ living grow system through Home Assistant. The point is the agentic AI-keeper
story, not the vegetables.

**v1 scope:** ONE DWC bucket, ONE basil plant. **v1 structure:** built for N
zones, diverse plants, and new grow methods without rewrites. *Narrow scope,
zones, diverse plants, and new grow methods, without rewrites. *Narrow scope,
right structure.*

## Two tiers (hard boundary)

1. **Reflex** ESPHome on the XIAO + Home Assistant automations/blueprints.
1. **Reflex**: ESPHome on the XIAO + Home Assistant automations/blueprints.
Deterministic safety: keep pH in band, cap any dose to a short pulse, run the
light schedule, alert if the air pump dies. **Runs even if the agent is
offline.** Sole authority for hard caps.
2. **Gardener** a plain Sonnet-class agent (`eden/gardener/`) with tools scoped
2. **Gardener**: a plain Sonnet-class agent (`eden/gardener/`) with tools scoped
ONLY to the garden. Reads sensor history, sets policy/dosing, journals, alerts.
A realtime voice agent can call it as a subagent and inherits exactly its
five-verb surface least privilege.
five-verb surface, least privilege.

The agent is never in a fast/life-critical loop. Mist-style sub-minute cycles
(future aeroponics) live in the reflex tier, declared by `GrowMethod.reflex_spec()`.

## The one-way doors (get these right now they're irreversible)
## The one-way doors (get these right now: they're irreversible)

1. **zone_id threaded through everything**, even at N=1. Entity ids, tool calls,
history, setpoints, journal are all zone-scoped. Adding pot #2 = a config block.
2. **Generic role-addressed tool API.** Five verbs (`read`, `actuate`,
`set_state`, `journal`, `alert` plus `history`) over a `(zone, role)` space.
`set_state`, `journal`, `alert`, plus `history`) over a `(zone, role)` space.
The agent never sees `dose_ph_down()` or an entity_id. New hardware/plants/
methods add DATA the tools already accept, never a new tool. `actuate` is the
ONLY path to a physical actuator; `set_state` handles non-physical state and
never touches hardware.
3. **Sensors/actuators addressed by ROLE → RESOURCE → entity_id.** A `Resource`
carries a `scope` (`zone` | `shared`). v1 is all `zone`, but addressing by
resource-id means a future shared nutrient tank / shelf light feeding multiple
zones is a config edit + a write-coordination step at the `actuate` chokepoint
not a re-plumb. ("Zones never share state" is physically false at v2; this
zones is a config edit + a write-coordination step at the `actuate` chokepoint,
not a re-plumb. ("Zones never share state" is physically false at v2; this
is the fix.)
4. **GrowMethod is the one strategy seam.** The loop calls `method.plan(readings,
recent_actions, profile, now) -> [Action]` and never names DWC or a pump.
`plan()` takes recent actions (dead-time/overshoot guard) and stays pure.
Methods also declare `reflex_spec()` what must live in firmware/HA. DWC is
Methods also declare `reflex_spec()`: what must live in firmware/HA. DWC is
the only method today; aeroponic/NFT are new files in `eden/methods/` that drop
in with zero loop edits.
5. **HA entity namespacing `eden_<zone>_<role>` from the first entity.** HA history
Expand All @@ -63,7 +63,7 @@ The agent is never in a fast/life-critical loop. Mist-style sub-minute cycles
The per-zone guard is an **HA blueprint** (`reflex/eden_zone_guard.blueprint.yaml`),
deployed into your Home Assistant config under `blueprints/automation/eden/`. Zone 2's
guard is a blueprint *instantiation*, not a forked automation. Eden does not keep a
drifting mirror the HA config is where HA loads it.
drifting mirror, the HA config is where HA loads it.

## Layout

Expand Down Expand Up @@ -108,10 +108,14 @@ uv run python -m eden # one pass over all zones (needs instance/.env)

Eden runs on a free compute base (a box with Home Assistant + a Claude API key)
plus off-the-shelf hydroponic hardware. **[docs/hardware.md](docs/hardware.md)** is
the full bill of materials every part, where to buy it, the verified XIAO
the full bill of materials, every part, where to buy it, the verified XIAO
ESP32-S3 pinout, and the germination steps to go from empty bucket to basil.

## Deliberately NOT built (YAGNI — add when a 2nd method/zone is real)
**[BUILD.md](BUILD.md)** is the step-by-step assembly guide. Read its Safety
section first: this build puts mains-powered equipment beside an open bucket of
water and has you handle a corrosive liquid.

## Deliberately NOT built (YAGNI: add when a 2nd method/zone is real)

No plugin registry / entry-points, no central Intent dispatch, no relational
config registry, no agent-side safety mirror (reflex is sole authority), no
Expand Down
52 changes: 28 additions & 24 deletions docs/hardware.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ by a Claude "Gardener" agent through Home Assistant. This is the parts list and
wiring to build the physical half.

**The compute is free.** Any always-on box running Home Assistant plus a Claude
API key runs the whole software stack you don't buy a server for this. What you
API key runs the whole software stack, you don't buy a server for this. What you
buy is the grow system, the sensors, the actuators and their drivers, one
microcontroller, and consumables. Rough all-in cost is **~$250–350** depending on
what you already own and which optional upgrades you take.

> Product links are Amazon ASINs / vendor SKUs that were current at the time of
> writing prices and availability drift, so confirm before you buy. Anything
> writing, prices and availability drift, so confirm before you buy. Anything
> from China (the pH probe especially) may carry an import tariff that a US
> vendor itemizes and Amazon hides in the sticker price.

Expand All @@ -20,8 +20,8 @@ what you already own and which optional upgrades you take.
### Grow system
| Part | Where | Notes |
|---|---|---|
| DWC bucket kit **VIVOSUN 1-bucket** | Amazon `B09JSBJXRF` | Bucket, 8" net basket, air pump + stone, clay pebbles, water-level gauge. The kit is the fastest way to a complete bucket. |
| *(optional)* Quieter air pump **EHEIM Air Pump 100** | Amazon `B0018CDWLS` | Buy-once upgrade over the kit's hummy pump. Worth it if the garden sits on a desk / in view on calls. |
| DWC bucket kit, **VIVOSUN 1-bucket** | Amazon `B09JSBJXRF` | Bucket, 8" net basket, air pump + stone, clay pebbles, water-level gauge. The kit is the fastest way to a complete bucket. |
| *(optional)* Quieter air pump, **EHEIM Air Pump 100** | Amazon `B0018CDWLS` | Buy-once upgrade over the kit's hummy pump. Worth it if the garden sits on a desk / in view on calls. |

### Controller (reflex tier)
| Part | Where | Notes |
Expand All @@ -31,34 +31,34 @@ what you already own and which optional upgrades you take.
### Sensors
| Part | Where | Notes |
|---|---|---|
| pH probe**DFRobot Gravity Industrial pH Sensor PRO** `SEN0169-V2` | Arrow (`arrow.com`) | 24/7 immersion-rated most consumer probes are not. Kit includes the signal-conditioning board that outputs 0–3 V analog. (DigiKey was backordered ~9 weeks; Arrow sells to individuals.) |
| ADC**ADS1115** 16-bit, I²C | Amazon (2-pack) | Reads the pH board's analog out. 16-bit matters avoid the 12-bit ADS1015. |
| Water temp **BOJACK DS18B20** waterproof kit | Amazon `B09NVWNGLQ` | The kit's adapter has the 4.7 kΩ 1-Wire pull-up built in. |
| *(optional)* EC/TDS pen **HM Digital COM-100** | Amazon `B0045LQFTK` | Manual nutrient-strength spot checks. Full EC automation is a v2 concern. |
| pH probe, **DFRobot Gravity Industrial pH Sensor PRO** `SEN0169-V2` | Arrow (`arrow.com`) | 24/7 immersion-rated, most consumer probes are not. Kit includes the signal-conditioning board that outputs 0–3 V analog. (DigiKey was backordered ~9 weeks; Arrow sells to individuals.) |
| ADC, **ADS1115** 16-bit, I²C | Amazon (2-pack) | Reads the pH board's analog out. 16-bit matters, avoid the 12-bit ADS1015. |
| Water temp, **BOJACK DS18B20** waterproof kit | Amazon `B09NVWNGLQ` | The kit's adapter has the 4.7 kΩ 1-Wire pull-up built in. |
| *(optional)* EC/TDS pen, **HM Digital COM-100** | Amazon `B0045LQFTK` | Manual nutrient-strength spot checks. Full EC automation is a v2 concern. |

### Actuators + drivers
| Part | Where | Notes |
|---|---|---|
| Dosing pump **Kamoer NKP 12V** peristaltic | Amazon `B07GWJ78FN` | Doses pH-Down. Peristaltic = precise small volumes, no cross-contamination. |
| MOSFETs **HiLetgo IRF520** 5-pack | Amazon `B01I1J14MO` | Low-side switching for the pump (on/off) and light (PWM). You use 2; keep spares for a v2 nutrient pump. |
| Grow light **VST 12V 4000K CRI90** white LED strip, 8 ft | Amazon `B09TDND2W4` | Bare strip; the XIAO PWM-dims it through a MOSFET. White (not "blurple") for a garden you actually want to look at. |
| Dosing pump, **Kamoer NKP 12V** peristaltic | Amazon `B07GWJ78FN` | Doses pH-Down. Peristaltic = precise small volumes, no cross-contamination. |
| MOSFETs, **HiLetgo IRF520** 5-pack | Amazon `B01I1J14MO` | Low-side switching for the pump (on/off) and light (PWM). You use 2; keep spares for a v2 nutrient pump. |
| Grow light, **VST 12V 4000K CRI90** white LED strip, 8 ft | Amazon `B09TDND2W4` | Bare strip; the XIAO PWM-dims it through a MOSFET. White (not "blurple") for a garden you actually want to look at. |
| **12V / 2A power brick** | from home or Amazon | Powers the pump and light rail. Reuse a spare router/HDD/LED adapter if you have one. |

### Chemistry + consumables
| Part | Where | Notes |
|---|---|---|
| pH-Down **General Hydroponics pH Control Kit** | Amazon `B000BNKWZY` | The pump doses from this. Kit also has pH-Up + test drops. |
| Calibration buffer **Apera AI1113** 4.00 + 7.00 | Amazon `B0CDHM6QBC` | Two-point calibration for the probe. Do this before trusting any reading. |
| Nutrients **GH MaxiGro** | Amazon `B00NQANQAC` | Single-part powder = simplest dosing for one bucket. |
| Rockwool**Grodan A-OK 1.5"**, 49-cube sheet | Amazon `B0742KTHNQ` | Soilless germination medium. Never use dirt soil fouls the DWC water and air stone. |
| Seeds Basil **"Italiano Classico Tigullio"** (cat# 13-7) | `growitalian.com` (Seeds from Italy) | The authentic Ligurian Genovese cultivar. Generic "Genovese" packets work too; this is the real one for pesto. |
| pH-Down, **General Hydroponics pH Control Kit** | Amazon `B000BNKWZY` | The pump doses from this. Kit also has pH-Up + test drops. |
| Calibration buffer, **Apera AI1113** 4.00 + 7.00 | Amazon `B0CDHM6QBC` | Two-point calibration for the probe. Do this before trusting any reading. |
| Nutrients, **GH MaxiGro** | Amazon `B00NQANQAC` | Single-part powder = simplest dosing for one bucket. |
| Rockwool, **Grodan A-OK 1.5"**, 49-cube sheet | Amazon `B0742KTHNQ` | Soilless germination medium. Never use dirt, soil fouls the DWC water and air stone. |
| Seeds, Basil **"Italiano Classico Tigullio"** (cat# 13-7) | `growitalian.com` (Seeds from Italy) | The authentic Ligurian Genovese cultivar. Generic "Genovese" packets work too; this is the real one for pesto. |

### From home / already owned
12V brick (if you have a spare), jumper wires, a soldering iron + solder (for the
LED strip or buy solderless strip connectors, ~$6), a machine running Home
LED strip, or buy solderless strip connectors, ~$6), a machine running Home
Assistant, and a Claude API key.

## Wiring XIAO ESP32-S3
## Wiring: XIAO ESP32-S3

These are the **verified pin assignments** baked into `esphome/eden-zone.yaml`.
Match them exactly or change both the wiring and the firmware substitutions.
Expand All @@ -67,11 +67,11 @@ Match them exactly or change both the wiring and the firmware substitutions.
|---|---|---|
| I²C SDA | `GPIO5` (D4) | ADS1115 SDA |
| I²C SCL | `GPIO6` (D5) | ADS1115 SCL |
| pH analog in | | pH signal board out → **ADS1115 A0** |
| pH analog in | *(none)* | pH signal board out → **ADS1115 A0** |
| Water temp (1-Wire) | `GPIO3` (D2) | DS18B20 data |
| pH-Down pump | `GPIO1` (D0) | IRF520 gate → pump `−`, pump `+` to 12V |
| Grow light (PWM) | `GPIO2` (D1) | IRF520 gate → LED strip `−`, strip `+` to 12V |
| **Air pump** | *(none)* | **Wall power, always on NOT wired to the MCU** |
| **Air pump** | *(none)* | **Wall power, always on, NOT wired to the MCU** |

Notes:
- ADS1115 sits at I²C address `0x48`, pH on channel A0, gain 4.096 (matches the
Expand All @@ -80,14 +80,18 @@ Notes:
Tie the XIAO ground, the 12V-brick ground, and the MOSFET sources together.
- The XIAO is USB-powered; the 12V brick powers only the pump and light rail.

## Safety — the reflex tier, in firmware (not the agent)
## Safety: the reflex tier, in firmware (not the agent)

This section is about protecting the *plant* from the software. For protecting
*yourself* while building, mains near water and acid handling, see the Safety
section at the top of [BUILD.md](../BUILD.md).

The whole point of the two-tier design is that safety lives in firmware and holds
even if the Gardener agent is offline or misbehaving:

- **The air pump is life support.** It runs on dumb, always-on wall power and is
**never** wired to the controller or exposed as an agent actuator. The firmware
only *reads* an `air_pump_ok` state for alerting it can't turn the pump off.
only *reads* an `air_pump_ok` state for alerting, it can't turn the pump off.
- **The pH dose has a hard 2-second auto-off** (`dose_cap_s` in the firmware). The
agent calls `turn_on`; the firmware turns it off. The agent physically cannot
run a dose long enough to overdose the bucket.
Expand All @@ -109,7 +113,7 @@ even if the Gardener agent is offline or misbehaving:

Entities then appear in Home Assistant as `sensor.eden_z1_ph`,
`sensor.eden_z1_water_temp`, `binary_sensor.eden_z1_air_pump_ok`,
`switch.eden_z1_ph_down_dose`, and `light.eden_z1_grow_light` exactly the
`switch.eden_z1_ph_down_dose`, and `light.eden_z1_grow_light`: exactly the
entity IDs `instance/zones.yaml` maps each role to.

A **second bucket** is a second node: `cp eden-z1.yaml eden-z2.yaml`, change
Expand All @@ -120,7 +124,7 @@ never collide and Home Assistant history is never re-keyed.

1. Soak the Grodan cubes ~1 hour in pH ~5.5 water (a few drops of pH-Down + the
GH test drops to check).
2. Seed the cubes and germinate **soilless** never in dirt, which fouls the DWC
2. Seed the cubes and germinate **soilless**: never in dirt, which fouls the DWC
water and air stone. A disposable lasagna pan with a poked-hole lid and ~¼" of
water makes a fine humidity dome.
3. Once the cube has roots, transplant the **whole cube** into the net basket,
Expand Down
2 changes: 1 addition & 1 deletion eden/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""Eden reusable AI-tended-garden domain logic. Imports nothing from instance/.
"""Eden, reusable AI-tended-garden domain logic. Imports nothing from instance/.
The personal garden config (the future "Hortus") lives in instance/ as data."""
4 changes: 2 additions & 2 deletions eden/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def main() -> None:
# Deterministic reflex-tier policy pass (runs without the model).
run_once(zones, tools, profiles, recent_by_zone={}, now=now())

print(f"eden zones={list(zones)} model={c.gardener_model}")
print(f"eden, zones={list(zones)} model={c.gardener_model}")
if not os.environ.get("ANTHROPIC_API_KEY"):
print("ANTHROPIC_API_KEY not set skipping the agentic pass")
print("ANTHROPIC_API_KEY not set; skipping the agentic pass")
return

with open(os.path.join(c.instance_dir, "prompt.md")) as f:
Expand Down
Loading
Loading