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>
Both hippocampus/config.rs and agent/config.rs read from the same
config file (~/.config/poc-agent/config.json5). Having two separate
implementations was a footgun — load_context_groups() was duplicated
three times across the codebase.
Merged into src/config.rs:
- Config (memory settings, global get()/reload())
- AppConfig (agent backend/model settings, figment-based loading)
- SessionConfig (resolved agent session, renamed from agent's Config)
- Single ContextGroup/ContextSource definition used everywhere
Eliminated: duplicate load_context_groups(), duplicate ContextGroup
definition in identity.rs, duplicate config file path constants.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
No more subcrate nesting — src/, agents/, schema/, defaults/, build.rs
all live at the workspace root. poc-daemon remains as the only workspace
member. Crate name (poc-memory) and all imports unchanged.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
poc-daemon (notification routing, idle timer, IRC, Telegram) was already
fully self-contained with no imports from the poc-memory library. Now it's
a proper separate crate with its own Cargo.toml and capnp schema.
poc-memory retains the store, graph, search, neuro, knowledge, and the
jobkit-based memory maintenance daemon (daemon.rs).
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
The clear sequence (Escape q C-c C-u) was disrupting Claude Code's
input state, causing nudge messages to arrive as blank prompts.
Simplified to just literal text + Enter.
Without -l, tmux send-keys treats spaces as key-name separators,
so multi-word messages like "This is your time" get split into
individual unrecognized key names instead of being typed as text.
This caused idle nudges to arrive as blank messages.
Add `poc-daemon afk` to immediately mark Kent as away, allowing the
idle timer to fire without waiting for the session active timeout.
Add `poc-daemon session-timeout <secs>` to configure how long after
the last message Kent counts as "present" (default 15min, persisted).
Fix block_reason() to report "kent present" and "in turn" states
that were checked in the tick but not in the diagnostic output.
Track activity level as an EWMA (exponentially weighted moving average)
driven by turn duration. Long turns (engaged work) produce large boosts;
short turns (bored responses) barely register.
Asymmetric time constants: 60s boost half-life for fast wake-up, 5-minute
decay half-life for gradual wind-down. Self-limiting boost formula
converges toward 0.75 target — can't overshoot.
- Add activity_ewma, turn_start, last_nudge to persisted state
- Boost on handle_response proportional to turn duration
- Decay on every tick and state transition
- Fix kent_present: self-nudge responses (fired=true) don't update
last_user_msg, so kent_present stays false during autonomous mode
- Nudge only when Kent is away, minimum 15s between nudges
- CLI: `poc-daemon ewma [VALUE]` to query or set
- Status output shows activity percentage
- Send PING after 120s of silence, disconnect after 30s with no PONG
- Reset backoff to base when a working connection drops (was registered)
- Validate channel membership before sending to channels
The ping timeout catches silent disconnects where the TCP connection
stays open but OFTC has dropped us. Previously we'd sit "connected"
indefinitely receiving nothing.
The daemon's claude -p subprocesses inherit hooks config, so every
agent LLM call triggered UserPromptSubmit → signal_user(), making the
idle timer think Kent was always active. The daemon was petting its
own tail.
Fix: set POC_AGENT=1 env var on all daemon claude subprocesses, and
return early from poc-hook when it's set.
Create lib.rs so all binaries can share library code directly instead
of shelling out to poc-memory. memory-search now calls search::search()
and store::Store::load() in-process instead of Command::new("poc-memory").
The load-context call still shells out (needs get_group_content moved
from main.rs to a library module).
Also: add search::format_results(), deduplicate extract_query_terms.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes:
1. Reset activity timestamps to now() on daemon restart instead of
loading stale values and suppressing with fired=true. Timers
count cleanly from restart.
2. Fix poc-hook to read hook_event_name (not type) from Claude Code's
JSON input. The hook was being called but never matched any event.
Also switch daemon_cmd from spawn() to status() since the command
takes 2ms — no reason to fire-and-forget.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Replace manual arg parsing with clap derive for the full command set.
Single source of truth for command names, args, and help text.
Add notify_timeout (default 2min) — controls how long after last
response before notifications inject via tmux instead of waiting
for the hook. Separate from idle_timeout (5min) which controls
autonomous prompts.
Improve `poc-daemon status` to show both timers with elapsed/configured
and block reason, replacing the terse one-liner.
Add new Status fields over capnp: idleTimeout, notifyTimeout,
sinceActivity, sinceUser, blockReason.
ExecStart in poc-daemon.service now uses `daemon` subcommand.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Several idle timer fixes and new inspection capabilities:
- Persist idle_timeout across daemon restarts (was reverting to 5min default)
- Set fired=true on load to suppress immediate fire from stale timestamps
- Add human-readable ISO timestamps to daemon-state.json for debugging
- Use to_string_pretty for readable state file
- Make save() public for RPC access
- Remove kb_idle_minutes() — go purely off message timestamps
- Add maybe_prompt_notification() with idle gate so notifications only
inject via tmux when truly idle, not during active sessions
- Add debug_json() for full state inspection with computed values
(would_fire, block_reason, all timers)
New RPC commands (schema @16-18):
poc-daemon idle-timeout <secs> — set idle timeout
poc-daemon save — force state persistence
poc-daemon debug — dump full internal state as JSON
Also: save state on clean shutdown, route module notifications through
maybe_prompt_notification before submitting to queue.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Three fixes:
1. Use read_until + from_utf8_lossy instead of AsyncBufRead::lines(),
which returns Err on invalid UTF-8. IRC isn't guaranteed UTF-8 —
Latin-1, Yiddish, etc. would crash the reader loop.
2. Handle CTCP requests (messages wrapped in \x01). Reply to VERSION
queries so the server stops retrying, and skip CTCP for notification
generation.
3. Log outgoing messages from the "send" command with append_log() so
they appear in IRC logs alongside incoming traffic.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
&str[..n] panics when n falls inside a multi-byte UTF-8 sequence.
This crashed the daemon when processing IRC messages containing
Hebrew/Yiddish characters (ehashman's messages hit byte 79-81).
Replace all byte-index truncation with chars().take(n).collect()
in tmux send_prompt preview, notification logging, and git context
truncation.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Move the notification daemon (IRC, Telegram, idle timer) and the
Claude Code hook binary into this repo as additional [[bin]] targets.
Single `cargo install --path .` now installs everything:
poc-memory — memory store CLI
memory-search — hook for memory retrieval
poc-daemon — notification/idle daemon (was claude-daemon)
poc-hook — Claude Code lifecycle hook (was claude-hook)
Renamed from claude-{daemon,hook} to poc-{daemon,hook} since the
infrastructure isn't tied to any specific AI assistant.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Move the hardcoded context priority groups from cmd_load_context()
into the config file as [context.NAME] sections. Add journal_days
and journal_max settings. The config parser handles section headers
with ordered group preservation.
Consolidate load-memory.sh into the memory-search binary — it now
handles both session-start context loading (first prompt) and ambient
search (subsequent prompts), eliminating the shell script.
Update install_hook() to reference ~/.cargo/bin/memory-search and
remove the old load-memory.sh entry from settings.json.
Add end-user documentation (doc/README.md) covering installation,
configuration, all commands, hook mechanics, and notes for AI
assistants using the system.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Add ~/.config/poc-memory/config.toml for user_name, assistant_name,
data_dir, projects_dir, and core_nodes. All agent prompts and
transcript parsing now use configured names instead of hardcoded
personal references.
`poc-memory daemon install` writes the systemd user service and
installs the memory-search hook into Claude's settings.json.
Scrubbed hardcoded names from code and docs.
Authors: ProofOfConcept <poc@bcachefs.org> and Kent Overstreet
- New spectral module: Laplacian eigendecomposition of the memory graph.
Commands: spectral, spectral-save, spectral-neighbors, spectral-positions,
spectral-suggest. Spectral neighbors expand search results beyond keyword
matching to structural proximity.
- Search: use StoreView trait to avoid 6MB state.bin rewrite on every query.
Append-only retrieval logging. Spectral expansion shows structurally
nearby nodes after text results.
- Fix panic in journal-tail: string truncation at byte 67 could land inside
a multi-byte character (em dash). Now walks back to char boundary.
- Replay queue: show classification and spectral outlier score.
- Knowledge agents: extractor, challenger, connector prompts and runner
scripts for automated graph enrichment.
- memory-search hook: stale state file cleanup (24h expiry).
- &PathBuf → &Path in memory-search.rs signatures
- Redundant field name in graph.rs struct init
- Add truncate(false) to lock file open
- Derive Default for Store instead of manual impl
- slice::from_ref instead of &[x.clone()]
- rsplit_once instead of split().last()
- str::repeat instead of iter::repeat().take().collect()
- is_none_or instead of map_or(true, ...)
- strip_prefix instead of manual slicing
Zero warnings on `cargo clippy`.