Current section

Files

Jump to
macula_tweann src brain_sup.erl
Raw

src/brain_sup.erl

%% @doc Supervisor for brain system components.
%%
%% This supervisor manages the inference and learning processes that
%% together form a brain system. The supervision strategy is one_for_all
%% because learning depends on inference state.
%%
%% == Architecture ==
%%
%% brain_sup (one_for_all)
%% |
%% +-- brain (inference GenServer)
%% | Forward propagation
%% | Visualization data
%% | Network state management
%% | Publishes: evaluated
%% |
%% +-- brain_learner (learning GenServer) [optional]
%% Plasticity rule application
%% Experience buffer
%% Weight updates
%% Subscribes to: evaluated, reward_received
%% Publishes: weights_updated
%%
%% == Communication ==
%%
%% Subsystems communicate via brain_pubsub (built on OTP pg):
%% - brain publishes evaluated after each forward pass
%% - brain_learner subscribes to receive activations
%% - brain_learner publishes weights_updated after learning
%% - brain subscribes to update its network weights
%%
%% == Future Expansion ==
%%
%% This supervisor is designed to accommodate future brain subsystems:
%% - brain_memory: Episodic memory storage
%% - brain_attention: Focus and salience control
%% - brain_prediction: Forward model / world model
%%
%% @copyright 2025 Macula.io
-module(brain_sup).
-behaviour(supervisor).
%% API
-export([
start_link/1,
start_link/2,
get_inference_pid/1,
get_learner_pid/1
]).
%% Supervisor callbacks
-export([init/1]).
-define(SUP_FLAGS, #{
strategy => one_for_all,
intensity => 3,
period => 5
}).
%%==============================================================================
%% API Functions
%%==============================================================================
%% @doc Start an unnamed brain supervisor.
%%
%% Options:
%% - `network' - The neural network (required)
%% - `learning_enabled' - Start learner process (default: false)
%% - `plasticity_rule' - Rule for learning (default: modulated)
%% - `learning_rate' - Learning rate (default: 0.01)
%% - `input_labels' - Labels for visualization
%% - `viz_enabled' - Enable visualization (default: true)
-spec start_link(map()) -> {ok, pid()} | {error, term()}.
start_link(Opts) ->
supervisor:start_link(?MODULE, Opts).
%% @doc Start a named brain supervisor.
-spec start_link(term(), map()) -> {ok, pid()} | {error, term()}.
start_link(Name, Opts) ->
supervisor:start_link(Name, ?MODULE, Opts).
%% @doc Get the PID of the inference process.
-spec get_inference_pid(pid()) -> pid() | undefined.
get_inference_pid(SupPid) ->
find_child(SupPid, brain).
%% @doc Get the PID of the learner process.
-spec get_learner_pid(pid()) -> pid() | undefined.
get_learner_pid(SupPid) ->
find_child(SupPid, brain_learner).
%%==============================================================================
%% Supervisor Callbacks
%%==============================================================================
init(Opts) ->
%% Extract options
Network = maps:get(network, Opts),
LearningEnabled = maps:get(learning_enabled, Opts, false),
InputLabels = maps:get(input_labels, Opts, []),
VizEnabled = maps:get(viz_enabled, Opts, true),
BrainId = maps:get(id, Opts, make_ref()),
%% Initialize pubsub for this brain
brain_pubsub:init(BrainId),
%% Build inference child spec
%% brain_id is passed so inference can publish/subscribe to events
InferenceOpts = #{
network => Network,
id => BrainId,
brain_id => BrainId,
input_labels => InputLabels,
viz_enabled => VizEnabled
},
InferenceSpec = #{
id => brain,
start => {brain, start_link, [InferenceOpts]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [brain]
},
%% Optionally add learner child spec
ChildSpecs = case LearningEnabled of
true ->
PlasticityRule = maps:get(plasticity_rule, Opts, modulated),
LearningRate = maps:get(learning_rate, Opts, 0.01),
BaselineReward = maps:get(baseline_reward, Opts, 0.0),
MaxBufferSize = maps:get(max_buffer_size, Opts, 1000),
%% Learner uses pubsub for communication with inference
LearnerOpts = #{
brain_id => BrainId,
enabled => true,
plasticity_rule => PlasticityRule,
learning_rate => LearningRate,
baseline_reward => BaselineReward,
max_buffer_size => MaxBufferSize
},
LearnerSpec = #{
id => brain_learner,
start => {brain_learner, start_link, [LearnerOpts]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [brain_learner]
},
[InferenceSpec, LearnerSpec];
false ->
[InferenceSpec]
end,
{ok, {?SUP_FLAGS, ChildSpecs}}.
%%==============================================================================
%% Internal Functions
%%==============================================================================
%% @private Find a child process by ID
find_child(SupPid, ChildId) ->
Children = supervisor:which_children(SupPid),
case lists:keyfind(ChildId, 1, Children) of
{ChildId, Pid, _Type, _Modules} when is_pid(Pid) ->
Pid;
_ ->
undefined
end.