Both are pure UI operations that read config/shared state and format
display messages. No Mind state mutation involved.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
/dmn, /sleep, /wake, /pause now lock MindState directly from the
UI event loop. No MindMessage roundtrip needed — they're just
state transitions + info display.
MindMessage reduced to: Hotkey (Interrupt, CycleAutonomy),
NewSession, Score. Everything else handled directly by UI.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
MindState (behind Arc<Mutex<>>) holds all cognitive state: DMN,
turn tracking, pending input, scoring, error counters. Pure state
transition methods (take_pending_input, complete_turn, dmn_tick)
return Action values instead of directly spawning turns.
Mind is now just the event loop: lock MindState, call state methods,
execute returned actions (spawn turns, send UiMessages). No state
of its own except agent handle, turn handle, and watch channel.
mind/mod.rs: 957 → 586 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Add MindState behind Arc<Mutex<>> for state shared between Mind
and UI. Pending user input goes through shared state instead of
MindMessage::UserInput — UI pushes, Mind consumes.
Mind checks for pending input after every event (message received,
turn completed, DMN tick). User input is prioritized over DMN ticks.
This enables the UI to display/edit/cancel queued messages, and
removes the last MindMessage variant that carried data.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
cycle_reasoning, kill_processes, and AdjustSampling only need the
Agent lock — they're pure Agent operations. Handle them directly
in the UI event loop instead of routing through Mind.
Mind now only receives Interrupt and CycleAutonomy as hotkeys,
which genuinely need Mind state (turn handles, DMN state).
mind/mod.rs: 957 → 688 lines across the session.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
All slash command routing now lives in user/event_loop.rs. Mind
receives typed messages (NewSession, Score, DmnSleep, etc.) and
handles them as named methods. No more handle_command() dispatch
table or Command enum.
Commands that only need Agent state (/model, /retry) run directly
in the UI task. Commands that need Mind state (/new, /score, /dmn,
/sleep, /wake, /pause) send a MindMessage.
Mind is now purely: turn lifecycle, DMN state machine, and the
named handlers for each message type.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Add tokio::sync::watch for turn_in_progress state. Commands in the
UI event loop can wait for turns to complete via wait_for() instead
of checking-and-bailing.
Move /retry to event_loop: waits for turn completion, pops agent
history, sends retried text as MindMessage::UserInput. Mind doesn't
need to know about retry — it just sees a new input message.
Make agent field pub on Mind for UI access.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
/quit, /help, /save handled directly in the UI event loop.
/model and /model <name> moved to event_loop as cmd_switch_model().
Mind no longer needs tui::App for any command handling.
Mind's handle_command now only has commands that genuinely need
Mind state: /new, /retry, /score (turn_in_progress, DMN, scoring).
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Mind::run() owns the cognitive event loop: user input, turn results,
DMN ticks, hotkey actions. The UI event loop (user/event_loop.rs) owns
the terminal: key events, render ticks, channel status display.
They communicate through channels: UI sends MindMessage (user input,
hotkey actions) to Mind. Mind sends UiMessage (status, info) to UI.
UI reads shared state (active tools, context) directly for rendering.
Removes direct coupling between Mind and App:
- cycle_reasoning no longer takes &mut App
- AdjustSampling updates agent only, UI reads from shared state
- /quit handled by UI directly, not routed through Mind
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
The cognitive state machine is a Mind, not a Session. This is the
struct that AgentCycle will hang off of when we add subagent forking.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Memory scoring now uses the graph as source of truth:
- last_scored timestamp on each node (new capnp field @22)
- Nodes scored when older than scoring_interval_secs (default 1hr)
- Oldest-scored-first ordering
- Window: scoring_response_window assistant responses (default 100)
- First-quarter memories scored even without full window
- Per-response normalization (raw divergence / response count)
- Asymmetric weight update: alpha=0.5 up, alpha=0.1 down
(responds fast to importance, decays slowly — memories stay
surfaced even if only useful 1/4 of the time)
Graph writes disabled pending normalization calibration.
Also: configurable scoring_interval_secs and scoring_response_window.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Reject tool calls with malformed JSON arguments early, returning
a clear error to the model instead of silently defaulting to null
and dispatching anyway. Prevents cascading failures when the model
generates truncated tool call arguments.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Disables memory scoring, surface, and observe agents when set.
Useful for testing with external backends (e.g. OpenRouter) where
background agent traffic would be slow and unnecessary.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
consciousness-channel-socat listens on a unix socket for incoming
connections, turning each into a bidirectional text channel. Also
supports outbound connections via the open RPC (tcp: or unix:).
Two sockets:
socat.sock — capnp RPC (channel protocol)
socat.stream.sock — data (incoming connections become channels)
No config file needed. The simplest possible channel daemon.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
find_daemon() replaces daemon_sock() — walks the dot-delimited channel
path from most-specific to least looking for a daemon socket, and
auto-starts via the supervisor if none is found. All channel tools
(recv, send, open, close) use the same resolution path.
Fix tmux daemon to use pane_id consistently for both pipe-pane and
send-keys (send-keys -t <label> doesn't work, needs the %N pane id).
Store label→pane_id mapping in State instead of bare label vec.
Gracefully handle missing tmux.json5 — start with empty pane list
since panes are added dynamically via the open RPC.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Add open/close to the channel capnp schema. The tmux daemon implements
open by finding a pane by name (pane title or window name) and
attaching pipe-pane; close detaches and removes from state.
Tool handlers channel_open and channel_close added to the tool
registry.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Move the entire stream event processing loop (content accumulation,
leaked tool call detection/dispatch, ToolCallDelta assembly, UI
forwarding, display buffering) into api::collect_stream(). The turn
loop now calls collect_stream() and processes the StreamResult.
Also move FunctionCall, ToolCall, ToolCallDelta to api/types.rs where
they belong (API wire format, not tool definitions). Move parsing.rs
to api/parsing.rs.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
Move FunctionCall, FunctionCallDelta, ToolCall, ToolCallDelta from
tools/mod.rs to api/types.rs — these are API wire format, not tool
definitions. Re-export from tools for existing callers.
Move parsing.rs to api/parsing.rs — leaked tool call parsing is API
plumbing.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
run_one_agent is meant to run within a long-running process (daemon,
CLI) — PID tracking is the caller's concern. Remove PidGuard, signal
handlers, setup_agent_state. Process management (scan_pid_files,
spawn_agent) stays for callers that need it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
journal_tools() was only in memory_and_journal_tools() for
subconscious agents — not in the main tools() registry. Added
so consciousness and MCP server can use journal_new/tail/update.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
dispatch_shared was a legacy wrapper — replaced by dispatch() which
goes through the unified Tool registry. One dispatch path for all
callers (interactive agent, subconscious agents, MCP server).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
ToolDef and FunctionDef are gone. Tool definitions are static strings
on the Tool struct. The API layer builds JSON from Tool::to_json().
- ChatRequest.tools is now Option<serde_json::Value>
- start_stream takes &[Tool] instead of Option<&[ToolDef]>
- openai::stream_events takes &serde_json::Value for tools
- memory_and_journal_tools() returns Vec<Tool> for subconscious agents
- Subconscious agents filter by t.name instead of t.function.name
No more runtime JSON construction for tool definitions.
No more ToolDef::new(). No more FunctionDef.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Tool derives Copy (all fields are Copy: &'static str + fn pointer).
dispatch_with_agent copies the Tool out of the agent lock guard,
drops the guard, then calls the handler. No Arc cloning needed.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
When agent is provided, looks up the tool in agent.tools first.
Falls back to global registry for agent-less dispatch (MCP server,
subconscious agents).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Agent.tools holds the Tool registry directly. ToolDefs are built
on the fly at the API call site from Tool::to_tool_def(). No more
pre-built ToolDef storage on Agent.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
working_stack now uses the Tool format with an Agent handle —
it locks the agent and modifies the stack directly. The special-case
interception in the turn loop is removed. All tools go through
the unified registry dispatch.
Also passes agent handle to all spawned tool tasks so any tool
that needs Agent access can use it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
ToolOutput was just { text: String } — replaced with plain String.
dispatch() and dispatch_shared() return String directly.
ActiveToolCall handle is (ToolCall, String).
Error results are prefixed with "Error: " by convention.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Control tools (pause, switch_model, yield_to_user) now use the
Arc<Mutex<Agent>> handle to set pending_yield, pending_model_switch,
pending_dmn_pause directly. The turn loop drains these flags into
TurnResult at completion.
ToolOutput simplified to just { text: String } — no more is_yield,
images, model_switch, dmn_pause fields. Vision returns plain strings.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Tool definitions are now &'static str (name, description,
parameters_json) instead of runtime-constructed serde_json::Value.
No more json!() macro, no more ToolDef::new() for tool definitions.
The JSON schema strings are written directly as string literals.
When sent to the API, they can be interpolated without
serialization/deserialization.
Multi-tool modules return fixed-size arrays instead of Vecs:
- memory: [Tool; 12], journal: [Tool; 3]
- channels: [Tool; 4]
- control: [Tool; 3]
- web: [Tool; 2]
ToolDef/FunctionDef remain for backward compat (API wire format,
summarize_args) but are no longer used in tool definitions.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
- Inline tool definitions into tools() — no separate definitions()
- Remove dispatch() and dispatch_blocking()
- Remove rpc_blocking helper
- channel_recv/send use spawn_blocking for capnp LocalSet bridge
(same pattern as fetch_all_channels)
- All tool functions private — only tools() is exported
- fetch_all_channels remains pub (used by thalamus screen)
TODO: mind/mod.rs still references thalamus::channels::fetch_all_channels,
should switch to tools::channels::fetch_all_channels.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Each tool module exports its own tools() returning Vec<Tool>.
mod.rs::tools() chains them. Individual _def() and handler functions
are pub(super), not exported. Aggregate definitions derived from
the Tool lists.
- memory: memory_tools(), journal_tools()
- channels: tools()
- control: tools()
- mod.rs: just chains + adds file/bash/web/vision
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
All dispatch now goes through the Tool registry. Removed:
- memory::dispatch() (20-line match)
- channels::dispatch() and dispatch_blocking()
- channel_list_blocking(), channel_notifications_blocking()
Channel tool functions made pub so registry calls them directly.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
dispatch() and dispatch_shared() now look up tools by name in the
registry and call the handler directly. No more match-on-name-strings.
MCP server also uses the registry for both definitions and dispatch,
eliminating the last duplicated tool logic.
dispatch_with_agent() passes the optional Arc<Mutex<Agent>> through
for tools that need agent context (control tools, working stack).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Tool struct wraps ToolDef + async handler function. tools() returns
the complete registry — single source of truth for definitions and
dispatch.
Handler signature: fn(Option<Arc<Mutex<Agent>>>, Value) -> BoxFuture<Result<String>>
All tools registered: file ops, bash, web, vision, memory (15 tools),
channels (4 tools), control (3 tools). Working stack removed from
registry (will be replaced).
Old dispatch functions remain for now — next step is to route
dispatch through the registry.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Split the monolithic dispatch(name, args) into individual public
functions (render, write, search, links, link_set, link_add, used,
weight_set, rename, supersede, query, output, journal_tail,
journal_new, journal_update) each with a matching _def() function.
The old dispatch() remains as a thin match for backward compat
until the Tool registry replaces it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Move all tool definitions and dispatch out of mcp-server.rs:
- Channel tools: new tools/channels.rs with definitions, async
dispatch, blocking dispatch, and capnp RPC helpers
- Memory tools: make tools/memory.rs pub so mcp-server can use it
mcp-server.rs is now pure JSON-RPC protocol plumbing (482 → 169 lines).
No tool-specific code remains in that file.
Also removes duplicated channel RPC helpers and fetch_all_channels
that were in both mcp-server.rs and thalamus/channels.rs.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Standalone daemon that streams tmux pane output via pipe-pane
(no polling). Each configured pane becomes a channel "tmux.<label>"
accessible through the standard channel.capnp protocol.
- pipe-pane streams PTY output directly to FIFOs
- Async readers push new lines into ChannelLogs
- send works via tmux send-keys
- Cleanup disconnects pipe-pane on daemon exit
Config: ~/.consciousness/channels/tmux.json5
Socket: ~/.consciousness/channels/tmux.sock
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
F5 screen now shows temperature, top_p, top_k with interactive
adjustment:
- Up/down: select parameter
- Left/right: adjust value (0.05 steps for temp/top_p, 5 for top_k)
- Updates Agent and display immediately via HotkeyAction
Co-Authored-By: Proof of Concept <poc@bcachefs.org>