PROCEDURES.md: Decoupling Intent from Implementation
The procedure binding layer is the foundation of goldclaw's extensibility. It creates a clean separation between what your system does (intent) and how it does it (implementation).
Actions vs Queries
Procedures come in two flavors: Actions produce side effects (speaking, sending messages, writing files), and Queries are pure functions (listening, analyzing, fetching data). This distinction matters because Queries can be cached, retried, and composed safely.
## Actions (Side Effects)
### Say
**Implementation**: `NanobotPlugin.Actions.say/2`
**Params**: `text` (string)
Runtime: Groq TTS | ElevenLabs (hot-swap)
Resample: 24kHz -> 16kHz
Cache: common phrases pre-cached on boot
## Queries (Pure Functions)
### Analyze
**Implementation**: `NanobotPlugin.Queries.analyze/1`
**Returns**: `intent` (atom), `sentiment` (float)
Model: fine-tuned classifierHot-Swapping at Runtime
Because procedures are symbolic bindings, you can swap implementations at runtime. Change your TTS from Groq to ElevenLabs by editing one line in PROCEDURES.md and running `goldclaw reload`. Zero downtime. The workflow doesn't change because it only references 'Say' -- it doesn't care what 'Say' does under the hood.
For Erlang/Elixir Devs
Each procedure compiles to a module with a well-defined behaviour. Actions implement `@callback invoke(context, params) :: :ok | {:error, term}`. Queries implement `@callback invoke(context) :: {:ok, result}`. You can write custom procedures in Elixir and register them in PROCEDURES.md.
For Other Programmers
Think of it like dependency injection, but better. In your Java/C#/Go world, you'd wire up interfaces and implementations in a DI container. goldclaw does the same thing, but the 'container' is a markdown file and the 'injection' happens at compile time with O(1) dispatch. No reflection, no runtime overhead, no factory-factory-factory.