ArchitectureReference

PROCEDURES.md: Decoupling Intent from Implementation

Jan 28, 2026 10 min

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.

markdown
## 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 classifier

Hot-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.