Files
wild-pc/docs/working/discussion.md
Paul Payne 8be129259c refactor: Restructure ~/.castle/ as the instance directory
Move all instance state into ~/.castle/ with clear separation:
- code/        — user project source (was components/)
- artifacts/   — generated specs, built content (was generated/, static/)
- data/        — service runtime data (was /data/castle/)
- secrets/     — credentials

castle.yaml moves to ~/.castle/castle.yaml as the canonical location.
Source paths use repo: prefix for git repo programs (castle-api, app)
and relative paths for user projects (code/central-context).

Add idempotent install.sh for bootstrapping infrastructure (Docker,
Caddy, MQTT, Postgres, Neo4j) with interactive service detection.

Remove components/ from repo (now in ~/.castle/code/), deinit
submodules, remove .gitmodules.
2026-03-09 22:03:37 -07:00

665 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Yes — that changes the design a lot, in a good way.
If **castle is not a general platform**, but a **curated machine for you + an AI assistant**, then the ontology should be optimized for:
* few concepts
* strong defaults
* predictable placement
* limited extension paths
* easy generation from natural language
That means castle should **not** try to model all possible machine semantics. Ubuntu already does that. Castle should define a **small opinionated overlay** on top of Ubuntu.
So Id stop trying to find the perfect universal ontology and instead design a **usefully incomplete one**.
## The design principle
Castle should answer:
> What are the few things I commonly want the machine to have or do?
From your examples, those are roughly:
* a **tool**
* a **site**
* a **service**
* a **worker/daemon**
* a **job**
* a **store/broker**
* maybe a **library/project**
That suggests the core user-facing abstraction should not be “artifact” or “namespace” or even “runtime.”
It should be something more like:
## Castles main abstraction: `unit`
A **unit** is a thing the castle machine knows how to host.
That thing may be:
* a Python tool
* a static site
* a web service
* a daemon
* a scheduled job
* a database
* a broker
Then beneath that, castle can have a smaller set of internal dimensions.
---
# A much simpler castle model
I think the practical model is:
```text
unit
kind
stack
uses
```
Where:
## `unit`
The named thing on the machine.
Examples:
* `reddit-tool`
* `docs-site`
* `api`
* `transform-worker`
* `morning-search`
* `postgres`
* `mqtt`
## `kind`
The machine role.
Examples:
* `tool`
* `site`
* `service`
* `worker`
* `job`
* `store`
* `broker`
## `stack`
How it is built/run.
Examples:
* `python`
* `rust`
* `react-static`
* `fastapi`
* `shell`
This is where language/build/runtime conventions collapse into one curated choice.
## `uses`
What castle subsystems or other units it depends on.
Examples:
* `postgres`
* `neo4j`
* `mqtt`
* `caddy`
* `reddit`
* `web`
This is much closer to how you actually want to talk to the AI.
---
# Why this is better
You dont want to say:
> create a deployment of kind service using runtime python with artifact class program and lifecycle systemd
You want to say:
> add a Python service that does X and uses Postgres
So castle should be shaped around **intentional unit kinds**, not abstract machine theory.
The machine theory should stay underneath as implementation.
---
# What castle can standardize
Because castle is curated, you can hardcode a lot:
## Always true on castle
* Ubuntu
* systemd
* Caddy
* standard directory layout
* standard logging
* standard service supervision
* standard way to expose commands on PATH
* standard way to run Python via `uv`
* standard way to build JS via `pnpm`
* standard way to define dependencies between units
That means users and AI do **not** need to specify those every time.
This is the key simplification move.
---
# Recommended fixed subsystems
Id make these built-in castle subsystems:
* **python** via `uv`
* **rust** via `cargo`
* **node/react** via `pnpm`
* **java** via one standard choice
* **caddy**
* **systemd**
* **postgres**
* **mqtt**
* maybe **neo4j**
* maybe **web-search ingestion** as a castle-native capability
Then the schema does not need to model arbitrary runtimes. It only needs to reference these known capabilities.
---
# Strong defaults for placement
You said castle can always use well-known spots. Good. Do that aggressively.
For example:
```text
/castle/units/<name>/src
/castle/units/<name>/build
/castle/units/<name>/env
/castle/units/<name>/state
/castle/units/<name>/config
/castle/units/<name>/logs
```
And for exposure:
```text
/castle/bin/<tool>
/castle/sites/<site>
/castle/data/<store>
```
Then castle itself can generate:
* symlinks into `/usr/local/bin`
* systemd units
* Caddy config
* env files
* state dirs
So the schema does not need to talk much about placement, because placement is mostly implied.
---
# The actual user-facing kinds
Id recommend just these:
## 1. `tool`
A command you can run from anywhere.
Examples:
* Python CLI
* Rust CLI
* shell helper
Defaults:
* exposed on PATH
* no long-running process
* build/install conventions by stack
Example:
```yaml
units:
summarize-reddit:
kind: tool
stack: python
```
---
## 2. `site`
A human-facing HTTP site served by Caddy.
Examples:
* React static site
* docs site
* maybe proxied app frontend later
Defaults:
* served by Caddy
* standard root dir
* standard local hostname or port
Example:
```yaml
units:
castle-web:
kind: site
stack: react-static
port: 8080
```
---
## 3. `service`
A long-running HTTP or TCP process supervised by systemd.
Examples:
* FastAPI app
* Rust API
* Java backend
Defaults:
* systemd-managed
* logs to journal
* optional Caddy exposure if HTTP
* standard environment/config paths
Example:
```yaml
units:
reddit-api:
kind: service
stack: fastapi
port: 9090
uses: [postgres, neo4j, reddit]
```
---
## 4. `worker`
A long-running non-HTTP daemon, often connected to broker/store inputs.
Examples:
* MQTT consumer
* pubsub transformer
* sync loop
* queue processor
Defaults:
* systemd-managed
* usually not publicly exposed
* can subscribe to broker/topic or poll/store
Example:
```yaml
units:
topic-transformer:
kind: worker
stack: python
uses: [mqtt]
subscribe: channel-b
publish: channel-y
```
This kind seems especially important for your goals.
---
## 5. `job`
A scheduled or one-shot task.
Examples:
* morning search
* report generation
* backup
* fetch and write JSON
Defaults:
* systemd timer
* writes to known output locations
* can use web-search capability
Example:
```yaml
units:
morning-z-search:
kind: job
stack: python
schedule: daily@08:00
uses: [web]
outputs:
- /castle/shared/search/z/
```
---
## 6. `store`
A built-in stateful service.
Examples:
* Postgres
* Neo4j
Defaults:
* castle manages lifecycle and placement
* other units reference by name
Example:
```yaml
units:
postgres:
kind: store
engine: postgres
neo4j:
kind: store
engine: neo4j
```
You may even decide these are so built-in they dont need to be declared unless customized.
---
## 7. `broker`
A built-in messaging service.
Examples:
* MQTT
Example:
```yaml
units:
mqtt:
kind: broker
engine: mqtt
```
Again, maybe built-in by default.
---
# The schema can now be tiny
Instead of a universal schema, use a compact one with per-kind defaults.
Example:
```yaml
units:
reddit-tool:
kind: tool
stack: python
description: CLI for querying reddit summaries
castle-web:
kind: site
stack: react-static
port: 8080
reddit-api:
kind: service
stack: fastapi
port: 9090
uses: [postgres, neo4j, reddit]
topic-transformer:
kind: worker
stack: python
uses: [mqtt]
subscribe: channel-b
publish: channel-y
morning-z-search:
kind: job
stack: python
schedule: "daily@08:00"
uses: [web]
output_dir: /castle/shared/search/z
```
That is probably closer to the right level.
---
# What `stack` should mean now
Since castle is curated, `stack` can absorb a lot:
Instead of:
* build system
* runtime
* conventions
* entrypoint rules
just use a known stack name.
Examples:
* `python`
* `fastapi`
* `rust`
* `react-static`
* `java-service`
* `shell`
Each stack implies:
* where source goes
* how to build
* how to run
* what files are expected
* how dependencies are installed
This is much simpler than separately modeling build/runtime/deployment for every case.
So for castle:
> **stack = curated implementation pattern**
Thats probably the right definition.
---
# What `uses` should mean
This is another very valuable simplification.
Instead of asking the user to define bindings and interfaces, let them say what the unit uses.
Examples:
* `postgres`
* `neo4j`
* `mqtt`
* `caddy`
* `web`
* `reddit`
* another unit name
Then castle knows how to wire it.
Example:
```yaml
uses: [postgres, neo4j, reddit]
```
could imply:
* env vars for local connection strings
* service ordering in systemd
* generated client config
* AI awareness that those capabilities are available
This is much more useful than formal dependency graphs.
---
# Under the hood
Internally, castle can still map to:
* artifacts
* runtimes
* systemd
* Caddy
* mount namespaces
* env files
* directories
But that should be hidden.
The AI assistant should primarily think in terms of:
* **What kind of unit is this?**
* **What stack should it use?**
* **What does it use?**
* **What should it be named?**
Thats enough to generate the machine config.
---
# So the real castle ontology may be
## User-facing
```text
unit
kind
stack
uses
```
## Machine-facing
```text
source
artifact
runtime
deployment
placement
```
This split is important.
You were trying to find one ontology for both humans and machines. You probably want two:
* a **simple command ontology** for you + AI
* a **lower-level implementation ontology** for castle internals
That resolves a lot of the tension.
---
# Example natural language mapping
“Add a python tool that does X”
becomes:
```yaml
name: x-tool
kind: tool
stack: python
```
“Make a web service that does Y”
becomes:
```yaml
name: y-service
kind: service
stack: fastapi
```
“Make a daemon that watches pubsub channel B and transforms the data and puts it on Y”
becomes:
```yaml
name: b-to-y
kind: worker
stack: python
uses: [mqtt]
subscribe: B
publish: Y
```
“Every morning, do a web search for Z and put the results in PATH as json files”
becomes:
```yaml
name: z-search
kind: job
stack: python
uses: [web]
schedule: daily@08:00
output_dir: /castle/shared/search/z
format: json
```
This is very manageable.
---
# Recommendation
I would make castle intentionally opinionated around:
* **7 unit kinds max**
* **58 stacks max**
* **a handful of built-in capabilities in `uses`**
* **fixed placement conventions**
* **automatic generation of systemd/Caddy/config**
That seems like the right balance of simplicity and extensibility for what you want.
The next useful step is to draft the **minimal v1 castle schema** with only those user-facing fields and strong defaults.