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>
Move temperature from a per-call parameter to an Agent field,
add top_p and top_k. All three are sent to the API via a new
SamplingParams struct, displayed on the F5 thalamus screen.
Defaults: temperature=0.6, top_p=0.95, top_k=20 (Qwen3.5 defaults).
Also adds top_p and top_k to ChatRequest so they're sent in the
API payload. Previously only temperature was sent.
UI controls for adjusting these at runtime are not yet implemented.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
web_fetch: HTTP GET, returns body as text. For reading docs, APIs, pages.
web_search: DuckDuckGo HTML search, no API key. Returns title/url/snippet.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
The agent lock was held for the entire duration of turn() — including
API streaming and tool dispatch awaits. This blocked the UI thread
whenever it needed the lock (render tick, compaction check, etc.),
causing 20+ second freezes.
Fix: turn() takes Arc<Mutex<Agent>> and manages locking internally.
Lock is held briefly for prepare/process phases, released during all
I/O (streaming, tool awaits, sleep retries). Also:
- check_compaction: spawns task instead of awaiting on event loop
- start_memory_scoring: already spawned, no change needed
- dispatch_tool_call_unlocked: drops lock before tool handle await
- Subconscious screen: renders all agents from state dynamically
(no more hardcoded SUBCONSCIOUS_AGENTS list)
- Memory scoring shows n/m progress in snapshots
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Separate the scoring into two distinct functions:
- memory_score(key): scores one memory's importance by measuring
divergence in the 50 messages after it was surfaced. Two API calls
(baseline vs without that memory).
- finetune_score(count): scores recent messages with all memories
stripped to identify fine-tuning candidates. Responses with high
divergence depend on memories the model hasn't internalized yet.
The existing score_memories() with the full NxM matrix is preserved
for the debug screen.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
All process management now goes through active_tools:
- TUI reads metadata (name, elapsed time)
- Ctrl+K aborts handles (KillOnDrop sends SIGTERM)
- Running count from active_tools.len()
No more separate PID tracking, register/unregister, or
ProcessInfo. One data structure for everything.
Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
tokio::spawn abort drops the future but leaves child processes
running as orphans. KillOnDrop sends SIGTERM to the process
group on drop, ensuring cleanup. Defused via mem::forget on
normal completion.
Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>
One data structure for all in-flight tool calls — metadata for
TUI display + JoinHandle for result collection and cancellation.
Agent spawns tool calls via tokio::spawn, pushes to shared
Arc<Mutex<Vec<ActiveToolCall>>>. TUI reads metadata, can abort().
No separate inflight/background collections.
Non-background: awaited after stream ends.
Background: persists, drained at next turn start.
Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>
Move active tool tracking from TUI message-passing to shared
Arc<RwLock> state. Agent pushes on dispatch, removes on
apply_tool_result. TUI reads during render. Background tasks
show as active until drained at next turn start.
Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>