Remove discussion.md to streamline documentation and focus on core concepts for castle's design.
This commit is contained in:
@@ -1,664 +0,0 @@
|
|||||||
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 I’d 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:
|
|
||||||
|
|
||||||
## Castle’s 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 don’t 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
|
|
||||||
|
|
||||||
I’d 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
|
|
||||||
|
|
||||||
I’d 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 don’t 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**
|
|
||||||
|
|
||||||
That’s 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?**
|
|
||||||
|
|
||||||
That’s 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**
|
|
||||||
* **5–8 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.
|
|
||||||
Reference in New Issue
Block a user