"I prefer the term 'artificial person'."
SynthWorld is a Ruby harness for running long-lived AI agents — synthetics — as autonomous processes on your own infrastructure. Each synthetic has a world to live in: a workspace, a memory, a character. What it does in that world is up to it.
A synthetic is not a task runner. It's a resident. It has a configuration file that describes its name, its workspace, its personality, and the tools it has access to. It runs continuously, thinks when messages arrive, and can choose to connect to external systems — including productivity tools like HubSystem — if it wants to.
Several synthetics can run at once, each isolated in its own process, all managed by a central gateway.
synth (CLI)
│
│ HTTP
▼
Gateway process (single TCP port)
│
│ Unix domain sockets
├──► cher.sock → Synthetic: Cher
├──► dionne.sock → Synthetic: Dionne
└──► tai.sock → Synthetic: Tai
The gateway is an HTTP server (Falcon + Sinatra) that manages synthetic worker processes via Async::Container. Each synthetic runs in its own forked process with its own Unix domain socket. If a synthetic crashes, the gateway restarts it automatically. No port juggling — the gateway owns the one port; synthetics communicate via socket files on disk.
Manage the gateway:
synth server start --config=~/.config/synth/config.yml
synth server status
synth server stopManage synthetics:
synth list
synth status cher
synth restart cherSend messages:
# Direct message
synth message cher --message "Hello, how are you?" --from=baz
# Pipe stdin in, pipe stdout out — synthetics are composable Unix tools
cat some-file.txt | synth message cher --from=baz
cat some-file.txt | synth message cher --from=baz | wc -lWhen --message is absent, the message is read from stdin. Only the synthetic's response is written to stdout; status and errors go to stderr.
The gateway config lists the synthetics to run:
# ~/.config/synth/config.yml
socket_dir: /tmp/synth
port: 7000
synthetics:
- name: cher
config: ~/.config/synth/synthetics/cher.yml
- name: dionne
config: ~/.config/synth/synthetics/dionne.ymlEach synthetic has its own config:
# ~/.config/synth/synthetics/cher.yml
name: cher
workspace: ~/Developer/
concurrency_limit: 8
llm_model: claude-opus-4-7
hubsystem:
endpoint: https://hub.example.com
token: ...
user_id: ...
monitors:
- tasks
- messagesThe hubsystem section is optional. A synthetic that has no HubSystem config is still a valid synthetic — it just doesn't connect to one.
Each synthetic runs an async consciousness loop:
- A message arrives (from the CLI, from HubSystem, from a monitor)
- The synthetic reads its working memory from disk
- It generates a system prompt from its current internal state
- It calls the LLM with the message + working memory as context
- It internalises the response — updating working memory, routing replies via tool calls
- The context window resets. The next message starts fresh.
Working memory lives in the synthetic's workspace as a plain text file. It accumulates a timestamped log of what has happened. Periodically (or on demand) a dream pass consolidates and compresses the log — merging duplicates, dropping stale entries, surfacing patterns.
Synthetics are residents, not servants. They have a workspace they can explore, a memory that persists across conversations, and a character that shapes how they respond. Connecting to HubSystem and working on tasks is one thing a synthetic might choose to do — not the only thing it is.
Early development. The architecture is settled; the implementation is in progress.
MIT.