Memory tool results (memory_render) are now pushed as
ConversationEntry::Memory with the node key, instead of plain
Messages. Remove loaded_nodes from ContextState — the debug
screen reads memory info from Memory entries in the conversation.
Surfaced memories from surface-observe are pushed as separate
Memory entries, reflections as separate system-reminder messages.
User input is no longer polluted with hook output.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Log ConversationEntry (with Memory/Message typing) instead of
raw Message. restore_from_log reads typed entries directly,
preserving Memory vs Message distinction across restarts.
Remove current.json snapshot and save_session — the append-only
log is the single source of truth. Remove dead read_all and
message_count methods. Add push_entry for logging typed entries.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Delete anthropic.rs (713 lines) — we only use OpenAI-compatible
endpoints (vLLM, OpenRouter). Simplify ApiClient to store base_url
directly instead of Backend enum.
SseReader now stores the serialized request payload and saves it
to ~/.consciousness/logs/failed-request-{ts}.json on stream timeout,
so failed requests can be replayed with curl for debugging.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
model_context_window() now reads from config.api_context_window
instead of guessing from model name strings. is_anthropic_model()
replaced with backend == "anthropic" checks. Dead model field
removed from AgentDef/AgentHeader.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
build_context_window loaded journal from a stale flat file and
assembled the full context. Now journal comes from the memory graph
and context is assembled on the fly. All that's needed is trimming
the conversation to fit the budget.
trim_conversation accounts for identity, journal, and reserve
tokens, then drops oldest conversation messages until it fits.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
The restore and compaction paths called build_context_window which
reads from the stale flat journal file, overwriting the journal we
loaded from the memory graph. Preserve the graph-loaded journal
across these operations.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Replace untyped message list with ConversationEntry enum:
- Message(Message) — regular conversation turn
- Memory { key, message } — memory content with preserved message
for KV cache round-tripping
Budget counts memory vs conversation by matching on enum variant.
Debug screen labels memory entries with [memory: key]. No heuristic
tool-name scanning.
Custom serde: Memory serializes with a memory_key field alongside
the message fields, deserializes by checking for the field.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Remove cached context_budget field and measure_budget(). Budget
is computed on demand via budget() which calls
ContextState::budget(). Each bucket counted from its typed source.
Memory split from conversation by identifying memory tool calls.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
context.messages is conversation-only now — remove conv_start
scanning. Memory counted from loaded_nodes (same as debug screen).
No subtraction heuristics.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
refresh_context_message was injecting personality into conversation
messages (assuming fixed positions that no longer exist). Replaced
with refresh_context_state which just re-measures and publishes.
conv_tokens now subtracts mem_tokens since memory tool results are
in the conversation message list.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
ContextState now owns everything in the context window:
system_prompt, personality, journal, working_stack, loaded_nodes,
and conversation messages. No duplication — each piece exists once
in its typed form.
assemble_api_messages() renders the full message list on the fly
from typed sources. measure_budget() counts each bucket from its
source directly. push_context() removed — identity/journal are
never pushed as messages.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Identity tokens from system_prompt + personality vec. Journal
from journal entries vec. Memory from loaded_nodes. Conversation
is the remainder. No string prefix matching.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Count journal tokens directly from Vec<JournalEntry> instead of
scanning message text for prefix strings. Type system, not string
typing.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Keep journal entries as structured data in ContextState. Render
to text only when building the context message. Debug screen reads
the structured entries directly — no parsing ## headers back out.
Compaction paths temporarily parse the string from build_context_window
back to entries (to be cleaned up when compaction is reworked).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Render journal entries directly with ## headers instead of going
through the plan_context/render_journal_text pipeline. 5% of
model context window (~6500 tokens for Qwen 128K). Simpler and
predictable.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Iterate journal entries backwards from the conversation cutoff,
accumulating within ~10K token budget (~8% of context window).
Stops when budget is full, keeps at least one entry. Much more
efficient than loading all entries and trimming.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Replace flat-file journal parser with direct store query for
EpisodicSession nodes. Filter journal entries to only those older
than the oldest conversation message (plus one overlap entry to
avoid gaps). Falls back to 20 recent entries when no conversation
exists yet.
Fixes: poc-agent context window showing 0 journal entries.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
spawn_agent returns Child handle + log_path. AgentCycleState stores
the Child, polls with try_wait() on each trigger to detect completion.
No more filesystem scanning to track agent lifecycle.
AgentSnapshot (Clone) sent to TUI for display. AgentInfo holds the
Child handle and stays in the state.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
spawn_agent() now returns SpawnResult { pid, log_path } so the
log path is known at spawn time. No more filesystem scanning.
AgentInfo carries log_path, TUI reads it directly.
F2 → Enter shows the actual agent log (stdout/stderr from the
poc-memory agent process), not the hook orchestration log.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Runner owns AgentCycleState, calls trigger() on each user message
instead of the old run_hook() JSON round-trip. Sends AgentUpdate
messages to TUI after each cycle.
TUI F2 screen reads agent state from messages instead of scanning
the filesystem on every frame. HookSession::from_fields() lets
poc-agent construct sessions without JSON serialization.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
- Remove POC_AGENT early return (was from old claude -p era)
- Split hook into run_agent_cycles() -> AgentCycleOutput (returns
memory keys + reflection) and format_agent_output() (renders for
Claude Code injection). poc-agent can call run_agent_cycles
directly and handle output its own way.
- Fix UTF-8 panic in runner.rs display_buf slicing (floor_char_boundary)
- Add priority debug label to API requests
- Wire up F2 agents screen: live pid status, output files, hook log
tail, arrow key navigation, Enter for log detail view
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Thread request priority through the API call chain to vLLM's
priority scheduler. Lower value = higher priority, with preemption.
Priority is set per-agent in the .agent header:
- interactive (runner): 0 (default, highest)
- surface-observe: 1 (near-realtime, watches conversation)
- all other agents: 10 (batch, default if not specified)
Requires vLLM started with --scheduling-policy priority.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Split the streaming pipeline: API backends yield StreamEvents through
a channel, the runner reads them and routes to the appropriate UI pane.
- Add StreamEvent enum (Content, Reasoning, ToolCallDelta, etc.)
- API start_stream() spawns backend as a task, returns event receiver
- Runner loops over events, sends content to conversation pane but
suppresses <tool_call> XML with a buffered tail for partial tags
- OpenAI backend refactored to stream_events() — no more UI coupling
- Anthropic backend gets a wrapper that synthesizes events from the
existing stream() (TODO: native event streaming)
- chat_completion_stream() kept for subconscious agents, reimplemented
on top of the event stream
- Usage derives Clone
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Tool call parsing was only in runner.rs, so subconscious agents
(poc-memory agent run) never recovered leaked tool calls from
models that emit <tool_call> as content text (e.g. Qwen via Crane).
Move the recovery into build_response_message where both code paths
share it. Leaked tool calls are promoted to structured tool_calls
and the content is cleaned, so all consumers see them uniformly.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
All log output was scattered across ~/.consciousness/memory/ (daemon,
task logs, LLM call logs), ~/.consciousness/agent-sessions/ (observe),
and only hook logs were already in the right place.
Move everything to ~/.consciousness/logs/ with agent-specific subdirs:
- daemon.log, daemon/ (task logs)
- {agent_name}/ (knowledge agent logs, e.g. surface-observe/, reflect/)
- llm/{caller}/ (LLM call logs)
- observe.log (poc-agent observe)
- hook-{session_id} (already correct)
- debug.log (already correct)
Also includes the session.rs and hook.rs fixes from the previous
session (sessions dir → ~/.consciousness/sessions/).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Separate identity files (loaded via source: "file" in context_groups)
from the memory store (data_dir). New identity_dir config field,
defaults to ~/.consciousness/identity/.
Also restrict subconscious agents to memory-only tools — no
filesystem write access. This prevents agents from creating stray
.md files in the memory directory.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
- thalamus/src/idle.rs: dream-start.sh path
- src/agent/dmn.rs: telegram/send.sh path
Part of the directory migration to make this an independent project.
migrate.rs was a one-time markdown→capnp conversion that's long done.
Remove it and update the identity.rs comment to reference the new
~/.consciousness/ path.
The consciousness project should stand independently of Claude Code.
All data, logs, sessions, and agent state now live under
~/.consciousness/ instead of being scattered across ~/.claude/memory/,
/tmp/claude-memory-search/, ~/.config/poc-memory/, and ~/.cache/.
Layout:
~/.consciousness/
*.capnp, *.bin, *.rkyv — store files
sessions/ — per-session state (seen sets, cookies)
logs/ — all logs (hook, agent, debug, dream)
agents/ — agent runtime state (pid files, output)
notifications/ — notification state
cache/ — transient data
Things that stay in ~/.claude/:
- projects/ (Claude Code transcripts)
- hooks/ (Claude Code hook system)
- telegram/ (shared integration)
- irc/ (shared integration)
- settings.json (Claude Code settings)
Debug log moves from /tmp/ to ~/.consciousness/logs/debug.log.
Session state moves from /tmp/claude-memory-search/ to sessions/.
Notifications move from ~/.claude/notifications/ to notifications/.
Provenance now flows as a function parameter through the entire tool
dispatch chain: thought::dispatch → memory::dispatch → store methods.
Removed task_local (TASK_AGENT), thread_local (TASK_PHASE), and env
var (POC_PROVENANCE) from the tool dispatch path. The env var remains
only as a fallback for non-tool paths (CLI commands, digest).
Phase names are passed from knowledge.rs → llm.rs → api.rs, and
api.rs updates the provenance string between steps. No globals needed.
- agent/tools/mod.rs: remove duplicated tool implementations, delegate
to thought::dispatch for shared tools, keep only agent-specific
tools (control, vision, working_stack)
- subconscious/api.rs: replace duplicated memory/tool dispatch with
thought::dispatch, use thought::all_definitions() for tool schemas
- Delete agent/tools/{bash,read,write,edit,grep,glob_tool,journal,memory}.rs
(now live in thought/)
Both poc-agent and subconscious agents now use the same tool
implementations through the thought layer. Agent-specific behavior
(node tracking in runner.rs, control tools) stays in agent/.
New src/thought/ module containing tools and infrastructure shared
between poc-agent and subconscious agents: memory operations, file
tools, bash, context window management.
Currently coexists with agent/tools/ — next step is to wire up both
agent/ and subconscious/ to use thought::dispatch instead of
duplicating the routing logic.
Move dbglog macro to lib.rs so it's available crate-wide regardless
of module compilation order.
- Add memory_rename tool (in-place rename, preserves content and links)
- Update rename.agent prompt to use memory_rename() instead of text output
- Fix {{rename}} placeholder to respect --target keys when provided
- Add format_rename_targets() for targeted rename runs
Links to nodes created after the conversation window start are
tagged with (new) in memory_render output. The surface prompt
tells the agent not to surface these — they're its own recent
output, not prior memories. Observe can still see and update them.
POC_MEMORIES_OLDER_THAN env var set from the oldest message
timestamp in the conversation window.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
- journal_new(name, title, body): name becomes the node key,
title goes in the ## heading. Agent picks short searchable names.
- Auto-dedup: if the key exists, append -2, -3, etc.
- CLI journal write also requires a name argument now.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
- journal_new: key is slugified title (agent names things properly)
- journal_tail: sort by created_at (immutable), not timestamp (mutable)
- journal_update: find latest by created_at
- {{latest_journal}}: query by NodeType::EpisodicSession, not "journal" key
- poc-memory journal write: requires a name argument
- Removed all journal#j-{timestamp}-{slug} patterns from:
- prompts.rs (rename candidates)
- graph.rs (date extraction, organize skip list)
- cursor.rs (date extraction)
- store/mod.rs (doc comment)
- graph.rs organize: filter by NodeType::Semantic instead of key prefix
- cursor.rs: use created_at for date extraction instead of key parsing
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
- journal_new: create EpisodicSession node with auto-generated key
- journal_tail: query by node_type, not by parsing a monolithic node
- journal_update: find latest EpisodicSession by timestamp
- No string key matching anywhere — all typed
- Fixes journal entries not appearing in 'poc-memory journal tail'
- Also: added --provenance/-p filter to 'poc-memory tail'
- Also: fix early return in surface_observe_cycle store load failure
- Also: scale max_turns by number of steps (50 per step)
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
- Bail command moved from hardcoded closure to external script
specified in agent JSON header ("bail": "bail-no-competing.sh")
- Runner executes script between steps with pid file path as $1,
cwd = state dir. Non-zero exit stops the pipeline.
- PID files simplified to just the phase name (no JSON) for easy
bash inspection (cat pid-*)
- scan_pid_files helper deduplicates pid scanning logic
- Timeout check uses file mtime instead of embedded timestamp
- PID file cleaned up on bail/error (not just success)
- output() tool validates key names (rejects pid-*, /, ..)
- Agent log files append instead of truncate
- Fixed orphaned derive and doc comment on AgentStep/AgentDef
- Phase written after bail check passes, not before
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
- output(key, value): write named results to agent state dir,
readable via {{input:key}} placeholder
- journal_tail(count): read last N journal entries
- journal_new(title, body): start new ## timestamped entry
- journal_update(body): append to last entry
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Replaced debug_visible bool with an Overlay enum. F1 shows the
context/debug screen (Ctrl+D still works as alias), F2 shows the
agents screen (placeholder for now — will show surface, observe,
reflect, journal status). Esc closes any overlay.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Personality is identity, not memory. Memory is nodes loaded during
the session via tool calls — things I've actively looked at.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
mem% was always 0 because memory_tokens was hardcoded to 0. Now
counts personality context + loaded nodes from memory tool calls.
Also calls measure_budget + publish_context_state after memory tool
dispatch so the debug screen updates immediately.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Exposes the full query language as a tool: filtering, sorting, field
selection, neighbor walks. Examples:
degree > 10 | sort weight | limit 5
neighbors('identity') | select strength
key ~ 'journal.*' | count
Also added query_to_string() in the parser so queries return strings
instead of printing to stdout. Updated memory-instructions-core to
list all current tools (added memory_query and journal, removed
CLI commands section and nonexistent memory_search_content).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
ContextSource::Store was handled identically to File — reading .md
files from disk. Now uses MemoryNode::load() to read from the capnp
store. This is why personality wasn't showing correctly in poc-agent:
store-sourced context groups (cognitive-modes, stuck-toolkit,
instructions, memory-instructions-core) were being looked up as
flat files and silently missing.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
- Removed write/search/mark_used static methods from MemoryNode —
those are store ops, not MemoryNode concerns
- Removed SearchResult duplicate — use query::engine::SearchResult
- Simplified Link to (String, f32) tuple — inline detection moved
to render()
- Collapsed tool definitions to one-liners
- Consolidated store-mutation tools into with_store() helper
- Supersede uses store directly instead of MemoryNode round-trip
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
MemoryNode moved from agent/memory.rs to hippocampus/memory.rs — it's
a view over hippocampus data, not agent-specific.
Store operations (set_weight, set_link_strength, add_link) moved into
store/ops.rs. CLI code (cli/graph.rs, cli/node.rs) and agent tools
both call the same store methods now. render_node() delegates to
MemoryNode::from_store().render() — 3 lines instead of 40.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>