Commit graph

395 commits

Author SHA1 Message Date
ProofOfConcept
c5ce6e515f fix seen set pollution from agent tool calls
The CLI render command was marking keys as seen in the user's session
whenever POC_SESSION_ID was set. Agent processes inherit POC_SESSION_ID
(they need to read the conversation and seen set), so their tool calls
to poc-memory render were writing to the seen file as a side effect —
bypassing the dedup logic in surface_agent_cycle.

Fix: set POC_AGENT=1 at the start of cmd_run_agent (covers all agents,
not just surface), and guard the CLI render seen-marking on POC_AGENT
being absent. Agents can read the seen set but only surface_agent_cycle
should write to it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:35:59 -04:00
ProofOfConcept
9782365b10 add test-conversation tool for debugging transcript parsing
Standalone binary that exercises TailMessages on a transcript file,
reporting progress and timing. Useful for isolating conversation
resolution issues from the full hook pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:28:01 -04:00
ProofOfConcept
a48cbe51a8 memory-search: move hook logic to library module, eliminate subprocesses
Move the hook logic from the memory-search binary into a library module
(poc_memory::memory_search) so poc-hook can call it as a direct function
instead of spawning a subprocess with piped stdin.

Also convert the node render call in surface_agent_cycle from
Command::new("poc-memory render") to a direct crate::cli::node::render_node()
call, eliminating another subprocess.

The memory-search binary remains as a thin CLI wrapper for debugging
(--hook reads from stdin) and inspection (show_seen).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:27:54 -04:00
ProofOfConcept
78c93dde4d surface agent: add surface_hooks config and reduce search hops
Add surface_hooks config field — list of hook event names that trigger
the surface agent (e.g. ["UserPromptSubmit"]). Empty list disables it.

Reduce surface agent search from 3-5 hops to 2-3 to keep prompt size
under the API endpoint's connection limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:27:40 -04:00
ProofOfConcept
9a0121250b agents: fix resolve_placeholders infinite loop
The placeholder resolver re-scanned from the beginning of the string
after each expansion. If expanded node content contained {{...}}
patterns (which core-personality does), those got expanded recursively.
Cyclic node references caused infinite string growth.

Fix: track a position offset that advances past each substitution,
so expanded content is never re-scanned for placeholders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:27:31 -04:00
ProofOfConcept
38816dc56e transcript: fix close-brace finder to track string boundaries
The backward JSON scanner (JsonlBackwardIter and TailMessages) was
matching } characters inside JSON strings — code blocks full of Rust
braces being the primary offender. This caused:

- Quadratic retry behavior on code-heavy transcripts (wrong object
  boundaries → serde parse failure → retry from different position)
- Inconsistent find_last_compaction_in_file offsets across calls,
  making detect_new_compaction fire repeatedly → context reload on
  every hook call → seen set growing without bound

Fix: add string-boundary tracking with escaped-quote handling to
the close-brace finder loop, matching the existing logic in the
depth-tracking loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:27:22 -04:00
Kent Overstreet
aa46b1d5a6 poc-agent: read context_groups from config instead of hardcoded list
- Remove MEMORY_FILES constant from identity.rs
- Add ContextGroup struct for deserializing from config
- Load context_groups from ~/.config/poc-agent/config.json5
- Check ~/.config/poc-agent/ first for identity files, then project/global
- Debug screen now shows what's actually configured

This eliminates the hardcoded duplication and makes the debug output
match what's in the config file.
2026-03-24 01:53:28 -04:00
Kent Overstreet
966219720a fix: mark surfaced keys as returned so --seen classifies them correctly
The surface agent result consumer in poc-hook was writing to the seen
file but not the returned file, so surfaced keys showed up as
"context-loaded" in memory-search --seen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:06:46 -04:00
Kent Overstreet
c0e6d5cfb3 distill: limit:1 to process one neighborhood per prompt
With limit:10, all seeds' neighborhoods got concatenated into one
massive prompt (878KB+), exceeding the model's context. One seed
at a time keeps prompts well under budget.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:28:00 -04:00
Kent Overstreet
e50d43bbf0 memory-search --seen: show current and previous seen sets separately
Instead of merging both into one flat list, display them as distinct
sections so it's clear what was surfaced in this context vs what
came from before compaction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:27:52 -04:00
Kent Overstreet
134f7308e3 surface agent: split seen_recent into seen_current/seen_previous placeholders
Two separate placeholders give the agent structural clarity about
which memories are already in context vs which were surfaced before
compaction and may need re-surfacing. Also adds memory_ratio
placeholder so the agent can self-regulate based on how much of
context is already recalled memories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:27:42 -04:00
Kent Overstreet
53b63ab45b seen_recent: cap at 20 roots total across both seen sets
Budget of 20 roots split between current and prev. Current gets
priority, prev fills the remainder. Prevents flooding the agent
with hundreds of previously surfaced keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:28:03 -04:00
Kent Overstreet
9512dc0a31 seen_recent: separate current vs pre-compaction seen sets
Present the two seen sets separately to the surface agent:
- Current: already in context, don't re-surface
- Pre-compaction: context was reset, re-surface if still relevant

This lets the agent re-inject important memories after compaction
instead of treating everything ever surfaced as "already shown."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:26:56 -04:00
Kent Overstreet
870b87df1b run surface agent on both UserPromptSubmit and PostToolUse
Extract surface_agent_cycle() and call from both hooks. Enables
memory surfacing during autonomous work (tool calls without human
prompts). Rate limiting via PID file prevents overlap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:47:58 -04:00
Kent Overstreet
b402746070 dedup nodes across seed neighborhoods in prompt building
Track which nodes have already been included and skip duplicates.
High-degree seed nodes with overlapping neighborhoods were pulling
the same big nodes dozens of times, inflating prompts to 878KB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:33:06 -04:00
Kent Overstreet
a8b560b5e1 lower neighborhood budget to 400KB to prevent oversized prompts
With core-personality + instructions + subconscious-notes adding
~200KB on top of the neighborhood, the 600KB budget pushed total
prompts over the 800KB guard. Lowered to 400KB so full prompts
stay under the limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 09:51:21 -04:00
Kent Overstreet
de36c0d39e memory-search: deduplicate seen set entries
mark_seen now takes the in-memory HashSet and checks before appending.
Prevents the same key being written 30+ times from repeated search hits
and context reloads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:00:26 -04:00
Kent Overstreet
38ad2ef4be surface.agent: instructions first, data last
Move core-personality and conversation to the end of the prompt.
The model needs to see its task before 200KB of conversation
context. Also: limit to 3 hops, 2-3 memories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:55:47 -04:00
Kent Overstreet
6fc10b0508 poc-hook: search last 8 lines for surface agent result marker
The agent output now includes logging (think blocks, tool calls)
before the final response. Search the tail instead of checking
only the last line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:49:08 -04:00
Kent Overstreet
d2255784dc surface.agent: tighten prompt to reduce tool call sprawl
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:46:52 -04:00
Kent Overstreet
42bd163942 TailMessages: only check first 200 bytes for type field
The type field is near the start of JSONL objects. Scanning the
full object (potentially megabytes for tool_results) was the
bottleneck — TwoWaySearcher dominated the profile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:35:40 -04:00
Kent Overstreet
e83d0184ea TailMessages: skip serde parse for non-message objects
Use memchr::memmem to check for "type":"user" or "type":"assistant"
in raw bytes before parsing. Avoids deserializing large tool_result
and system objects entirely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:32:59 -04:00
Kent Overstreet
ecc2cb7b20 replace tail_messages with TailMessages iterator
TailMessages is a proper iterator that yields (role, text, timestamp)
newest-first. Owns the mmap internally. Caller decides when to stop.

resolve_conversation collects up to 200KB, then reverses to
chronological order. No compaction check needed — the byte budget
naturally limits how far back we scan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:22:17 -04:00
Kent Overstreet
6c41b50e04 JsonlBackwardIter: use memrchr3 for SIMD-accelerated scanning
Replaces byte-by-byte backward iteration with memrchr3('{', '}', '"')
which uses SIMD to jump between structurally significant bytes. Major
speedup on large transcripts (1.4GB+).

Also simplifies tail_messages to use a byte budget (200KB) instead
of token counting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:11:30 -04:00
Kent Overstreet
d7d631d77d tail_messages: parse each object once, skip non-message types early
Was parsing every object twice (compaction check + message extract)
and running contains_bytes on every object for the compaction marker.
Now: quick byte pre-filter for "user"/"assistant", parse once, check
compaction after text extraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:05:04 -04:00
Kent Overstreet
e39096b787 add tail_messages() for fast reverse transcript scanning
Reverse-scans the mmap'd transcript using JsonlBackwardIter,
collecting user/assistant messages up to a token budget, stopping
at the compaction boundary. Returns messages in chronological order.

resolve_conversation() now uses this instead of parsing the entire
file through extract_conversation + split_on_compaction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:02:11 -04:00
Kent Overstreet
a03bf390a8 render: mark node as seen when POC_SESSION_ID is set
When poc-memory render is called inside a Claude session, add the
key to the seen set so the surface agent knows it's been shown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:43:46 -04:00
Kent Overstreet
41a9a1d2da add surface.agent — async memory retrieval agent
Fires on each UserPromptSubmit, reads the conversation via
{{conversation}}, checks {{seen_recent}} to avoid re-surfacing,
searches the memory graph, and outputs a key list or nothing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-22 02:35:15 -04:00
Kent Overstreet
4183b28b1d add {{conversation}} and {{seen_recent}} placeholders for surface agent
{{conversation}} reads POC_SESSION_ID, finds the transcript, extracts
the last segment (post-compaction), returns the tail ~100K chars.

{{seen_recent}} merges current + prev seen files for the session,
returns the 20 most recently surfaced memory keys with timestamps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:27:43 -04:00
Kent Overstreet
85307fd6cb surface agent infrastructure: hook spawn, seen set rotation, config
Surface agent fires asynchronously on UserPromptSubmit, deposits
results for the next prompt to consume.  This commit adds:

- poc-hook: spawn surface agent with PID tracking and configurable
  timeout, consume results (NEW RELEVANT MEMORIES / NO NEW), render
  and inject surfaced memories, observation trigger on conversation
  volume
- memory-search: rotate seen set on compaction (current → prev)
  instead of deleting, merge both for navigation roots
- config: surface_timeout_secs option

The .agent file and agent output routing are still pending.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
53c5424c98 remove redundant 'response NKB' log line
Already shown in === RESPONSE === section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
f70d108193 api: include turn/payload/message count in API error messages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
be2b499978 remove claude CLI subprocess code from llm.rs
All LLM calls now go through the direct API backend. Removes
call_model, call_model_with_tools, call_sonnet, call_haiku,
log_usage, and their dependencies (Command, prctl, watchdog).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
04dffa2184 add call_simple for non-agent LLM calls
audit, digest, and compare now go through the API backend via
call_simple(), which logs to llm-logs/{caller}/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
e3f7d6bd3c remove --debug flag from agent run
The log file has everything now; --debug was redundant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
543e1bdc8a logging: single output stream through caller's log closure
Pass the caller's log closure all the way through to api.rs instead
of creating a separate eprintln closure in llm.rs. Everything goes
through one stream — prompt, think blocks, tool calls with args,
tool results with content, token counts, final response.

CLI uses println (stdout), daemon uses its task log. No more split
between stdout and stderr.

Also removes the llm-log file creation from knowledge.rs — that's
the daemon's concern, not the agent runner's.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:23:30 -04:00
Kent Overstreet
e74d533748 fix: improve blocking read end-of-response detection
The original '>' detection was too broad and caught tool output lines.
Now we look for '> X: ' pattern (user prompt with speaker prefix) to
detect the start of a new user input, which marks the end of the
previous response.
2026-03-21 23:40:48 -04:00
Kent Overstreet
a3acf0a681 feat: add --block flag to poc-agent read
The --block flag makes poc-agent read block until a complete response
is received (detected by a new user input line starting with '>'),
then exit. This enables smoother three-way conversations where one
instance can wait for the other's complete response without polling.

The implementation:
- Added cmd_read_inner() with block parameter
- Modified socket streaming to detect '>' lines as response boundaries
- Added --block CLI flag to Read subcommand

The --follow flag continues to stream indefinitely.
The --block flag reads one complete response and exits.
Neither flag exits immediately if there's no new output.
2026-03-21 23:39:12 -04:00
Kent Overstreet
8a83f39734 feat: trigger observation agent on conversation volume
The hook now tracks transcript size and queues an observation agent
run every ~5K tokens (~20KB) of new conversation. This makes memory
formation reactive to conversation volume rather than purely daily.

Configurable via POC_OBSERVATION_THRESHOLD env var. The observation
agent's chunk_size (in .agent file) controls how much context it
actually processes per run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:22:43 -04:00
Kent Overstreet
0baa80a4c7 refactor: restructure distill, linker, split agent prompts
Move data sections before instructions (core at top, subconscious +
notes at bottom near task). Deduplicate guidelines that are now in
memory-instructions-core-subconscious. Compress verbose paragraphs
to bullet points.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:04:57 -04:00
Kent Overstreet
8db59fe2db fix: ensure all agents have both core and subconscious instructions
All 18 agents now include:
- {{node:memory-instructions-core}} — tool usage instructions
- {{node:memory-instructions-core-subconscious}} — subconscious framing
- {{node:subconscious-notes-{agent_name}}} — per-agent persistent notes

The subconscious instructions are additive, not a replacement for
the core memory instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:51:56 -04:00
Kent Overstreet
1a94ef1f1c fix: cap neighborhood size in agent prompts to prevent oversized prompts
When building the {{neighborhood}} placeholder for distill and other
agents, stop adding full neighbor content once the prompt exceeds
600KB (~150K tokens). Remaining neighbors get header-only treatment
(key + link strength + first line).

This fixes distill consistently failing on high-degree nodes like
inner-life-sexuality-intimacy whose full neighborhood was 2.5MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:44:59 -04:00
Kent Overstreet
653da40dcd cleanup: auto-fix clippy warnings in poc-memory
Applied cargo clippy --fix for collapsible_if, manual_char_comparison,
and other auto-fixable warnings.
2026-03-21 19:42:38 -04:00
Kent Overstreet
3640de444b cleanup: fix clippy warnings in daemon.rs
- Remove dead code (job_split_one function never called)
- Fix needless borrows (ctx.log_line(&format! -> format!))
- Fix slice clone ([key.clone()] -> std::slice::from_ref(&key))
- Collapse nested if statements
- Fix unwrap after is_some check
- Remove redundant closures in task spawning

Reduces daemon.rs from 2030 to 1825 lines.
2026-03-21 19:42:03 -04:00
Kent Overstreet
a0d8b52c9a feat: subconscious agent notes and instructions
Each consolidation agent now has its own persistent notes node
(subconscious-notes-{agent_name}) loaded via template substitution.
Agents can read their notes at the start of each run and write
updates after completing work, accumulating operational wisdom.

New node: memory-instructions-core-subconscious — shared framing
for background agents ("you are an agent of PoC's subconscious").

Template change: {agent_name} is substituted before {{...}} placeholder
resolution, enabling per-agent node references in .agent files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:38:01 -04:00
Kent Overstreet
acc878b9a4 ui: two-column layout for conversation pane with marker gutter
Split conversation pane into 2-char gutter + text column. Gutter shows
● markers at turn boundaries (Cyan for user, Magenta for assistant),
aligned with the input area's ' > ' gutter.

Key changes:
- Added Marker enum (None/User/Assistant) and parallel markers vec
- Track turn boundaries via pending_marker field
- New draw_conversation_pane() with visual row computation for wrapping
- Both gutter and text scroll synchronously by visual line offset

This fixes the wrapping alignment issue where continuation lines
aligned under markers instead of under the text.
2026-03-21 19:15:13 -04:00
Kent Overstreet
78b22d6cae fix: buffer streaming tokens in observe log for readable transcripts
The observe log was writing each TextDelta SSE token as a separate
line, making poc-agent read show word-by-word fragments and causing
the read cursor to advance past partial responses.

Now TextDelta and Reasoning tokens are buffered and flushed as
complete messages on turn boundaries (tool calls, user input, etc).
The socket path (read -f) still streams live.

Also fixed a potential deadlock: replaced blocking_lock() with
.lock().await on the shared logfile mutex.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Qwen 3.5 27B <noreply@qwen.ai>
2026-03-21 16:51:12 -04:00
Kent Overstreet
5ae33a48ab refactor: typed args for grep, bash, and vision tools
Convert remaining tools from manual args["key"].as_str() parsing to
serde Deserialize structs. Also removes the now-unused get_str()
helper from grep.rs and simplifies capture_tmux_pane() signature
(takes lines directly instead of re-parsing args).

All 7 tool modules now use the same typed args pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:31:34 -04:00
Kent Overstreet
74f05924ff refactor: use typed Deserialize structs for tool arguments
Convert read_file, write_file, edit_file, and glob from manual
args["key"].as_str() parsing to serde_json::from_value with typed
Args structs. Gives type safety, default values via serde attributes,
and clearer error messages on missing/wrong-type arguments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:28:10 -04:00
Kent Overstreet
29db4ff409 refactor: extract identity/context assembly into identity.rs
Move file discovery (CLAUDE.md/POC.md, memory files, people/ glob),
prompt assembly, and context_file_info from config.rs into identity.rs.

All extracted functions are pure — they take paths and return strings,
with no dependency on AppConfig. config.rs calls into identity.rs
(one-way dependency).

config.rs: 663 → 440 lines (-223)
identity.rs: 232 lines (new)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:19:27 -04:00