- store/types.rs: sanitize timestamps on capnp load — old records had
raw offsets instead of unix epoch, breaking sort-by-timestamp queries
- agents/api.rs: drain reasoning tokens from UI channel into LLM logs
so we can see Qwen's chain-of-thought in agent output
- agents/daemon.rs: persistent task queue (pending-tasks.jsonl) —
tasks survive daemon restarts. Push before spawn, remove on completion,
recover on startup.
- api/openai.rs: only send reasoning field when explicitly configured,
not on every request (fixes vllm warning)
- api/mod.rs: add 600s total request timeout as backstop for hung
connections
- Cargo.toml: enable tokio-console feature for task introspection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- observation.agent: rewritten to navigate graph and prefer refining
existing nodes over creating new ones. Identity-framed prompt,
goals over rules.
- poc-memory edit: opens node in $EDITOR, writes back on save,
no-op if unchanged
- daemon: remove extra_workers (jobkit tokio migration dropped it),
remove sequential chaining of same-type agents (in-flight exclusion
is sufficient)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lower threshold excluded too many neighbors, causing "query
returned no results (after exclusion)" failures and underloading
the GPU. Now only moderately-connected neighbors (score > 0.3) are
excluded, balancing collision prevention with GPU utilization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a prompt exceeds the size guard, dump it to a timestamped file
with agent name, size, and seed node keys. Makes it easy to find
which nodes are blowing up prompts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes:
1. Sanitize tool call arguments before pushing to conversation
history — vllm re-parses them as JSON on the next request and
crashes on invalid JSON from a previous turn. Malformed args now
get replaced with {} and the model gets an error message telling
it to retry with valid JSON.
2. Remove is_split special case — split goes through the normal
job_consolidation_agent path like all other agents.
3. call_for_def always uses API when api_base_url is configured,
regardless of tools field. Remove tools field from all .agent
files — memory tools are always provided by the API layer.
Also adds prompt size guard (800KB max) to catch oversized prompts
before they hit the model context limit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove is_split special case in daemon — split now goes through
job_consolidation_agent like all other agents
- call_for_def uses API whenever api_base_url is configured, regardless
of tools field (was requiring non-empty tools to use API)
- Remove "tools" field from all .agent files — memory tools are always
provided by the API layer, not configured per-agent
- Add prompt size guard: reject prompts over 800KB (~200K tokens) with
clear error instead of hitting the model's context limit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config now derives serde::Deserialize with #[serde(default)] for all
fields. Path fields use custom deserialize_path/deserialize_path_opt
for ~ expansion. ContextGroup and ContextSource also derive Deserialize.
try_load_shared() is now 20 lines instead of 100: json5 → serde →
Config directly, then resolve API settings from the model/backend
cross-reference.
Removes MemoryConfigRaw intermediate struct entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Active agent types for consolidation cycles are now read from
config.json5 memory.agent_types instead of being hardcoded in
scoring.rs. Adding or removing agents is a config change, not
a code change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace per-field ConsolidationPlan struct with HashMap<String, usize>
counts map. Agent types are no longer hardcoded in the struct — add
agents by adding entries to the map.
Active agents: linker, organize, distill, separator, split.
Removed: transfer (redundant with distill), connector (rethink later),
replay (not needed for current graph work).
Elo-based budget allocation now iterates the map instead of indexing
a fixed array. Status display and TUI adapted to show dynamic agent
lists.
memory-instructions-core v13: added protected nodes section — agents
must not rewrite core-personality, core-personality-detail, or
memory-instructions-core. They may add links but not modify content.
High-value neighbors should be treated with care.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor cmd_render into render_node() that returns a String —
reusable by both the CLI and agent placeholders.
Add {{seed}} placeholder: renders each seed node using the same
output as poc-memory render (content + deduped footer links). Agents
see exactly what a human sees — no special formatting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Warn when content contains render artifacts (poc-memory render key
embedded in prose — should be just `key`) or malformed → references.
Soft warnings on stderr, doesn't block the write.
Catches agent output that accidentally includes render-decorated
links, preventing content growth from round-trip artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Render now detects neighbor keys that already appear in the node's
content and omits them from the footer link list. Inline references
serve as the node's own navigation structure; the footer catches
only neighbors not mentioned in prose.
Also fixes PEG query parser to accept hyphens in field names
(content-len was rejected).
memory-instructions-core updated to v12: documents canonical inline
link format (→ `key`), adds note about normalizing references when
updating nodes, and guidance on splitting oversized nodes.
Content is never modified for display — render is round-trippable.
Agents can read rendered output and write it back without artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
linker: sort:isolation*0.7+recency(linker)*0.3
Prioritizes nodes in isolated communities that haven't been linked
recently. Bridges poorly-connected clusters into the main graph.
organize: sort:degree*0.5+isolation*0.3+recency(organize)*0.2
Prioritizes high-degree hubs in isolated clusters that haven't been
organized recently. Structural work where it matters most.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add sort:field*weight+field*weight+... syntax for weighted multi-field
sorting. Each field computes a 0-1 score, multiplied by weight, summed.
Available score fields:
isolation — community isolation ratio (1.0 = fully isolated)
degree — graph degree (normalized to max)
weight — node weight
content-len — content size (normalized to max)
priority — consolidation priority score
recency(X) — time since agent X last visited (sigmoid decay)
Example: sort:isolation*0.7+recency(linker)*0.3
Linker agents prioritize isolated communities that haven't been
visited recently.
Scores are pre-computed per sort (CompositeCache) to avoid redundant
graph traversals inside the sort comparator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add community_isolation() to Graph — computes per-community ratio of
internal vs total edge weight. 1.0 = fully isolated, 0.0 = all edges
external.
New query: sort:isolation — sorts nodes by their community's isolation
score, most isolated first. Useful for aiming organize agents at
poorly-integrated knowledge clusters.
New CLI: poc-memory graph communities [N] [--min-size M] — lists
communities sorted by isolation with member preview. Reveals islands
like the Shannon theory cluster (3 nodes, 100% isolated, 0 cross-edges)
and large agent-journal clusters (20-30 nodes, 95% isolated).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track which nodes are being processed across all concurrent agents.
When an agent claims seeds, it adds them and their strongly-connected
neighbors (score = link_strength * node_weight > 0.15) to a shared
HashSet. Concurrent agents filter these out when running their query,
ensuring they work on distant parts of the graph.
This replaces the eager-visit approach with a proper scheduling
mechanism: the daemon serializes seed selection while parallelizing
LLM work. The in-flight set is released on completion (or error).
Previously: core-personality rewritten 12x, irc-regulars 10x, same
node superseded 12x — concurrent agents all selected the same
high-degree hub nodes. Now they'll spread across the graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move visit recording from after LLM completion to immediately after
seed selection. With 15 concurrent agents, they all queried the same
graph state and selected the same high-degree seeds (core-personality
written 12x, irc-regulars 10x). Now the not-visited filter sees the
claim before concurrent agents query.
Narrows the race window from minutes (LLM call duration) to
milliseconds (store load to visit write). Full elimination would
require store refresh before query, but this handles the common case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add {{neighborhood}} placeholder for agent prompts: full seed node
content + ranked neighbors (score = link_strength * node_weight) with
smooth cutoff, minimum 10, cap 25, plus cross-links between included
neighbors.
Rewrite organize.agent prompt to focus on structural graph work:
merging duplicates, superseding junk, calibrating weights, creating
concept hubs.
Add weight-set CLI command for direct node weight manipulation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs: upsert_provenance didn't update node.timestamp, so history
showed the original creation date for every version. And native memory
tools (poc-agent dispatch) didn't set POC_PROVENANCE, so all agent
writes showed provenance "manual" instead of "agent:organize" etc.
Fix: set node.timestamp = now_epoch() in upsert_provenance. Thread
provenance through memory::dispatch as Option<&str>, set it via
.env("POC_PROVENANCE") on each subprocess Command. api.rs passes
"agent:{name}" for daemon agent calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tools:
- Add native memory_render, memory_write, memory_search,
memory_links, memory_link_set, memory_link_add, memory_used
tools to poc-agent (tools/memory.rs)
- Add MCP server (~/bin/memory-mcp.py) exposing same tools
for Claude Code sessions
- Wire memory tools into poc-agent dispatch and definitions
- poc-memory daemon agents now use memory_* tools instead of
bash poc-memory commands — no shell quoting issues
Distill agent:
- Rewrite distill.agent prompt: "agent of PoC's subconscious"
framing, focus on synthesis and creativity over bookkeeping
- Add {{neighborhood}} placeholder: full seed node content +
all neighbors with content + cross-links between neighbors
- Remove content truncation in prompt builder — agents need
full content for quality work
- Remove bag-of-words similarity suggestions — agents have
tools, let them explore the graph themselves
- Add api_reasoning config option (default: "high")
- link-set now deduplicates — collapses duplicate links
- Full tool call args in debug logs (was truncated to 80 chars)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
poc-memory now reads from poc-agent's config.json5 as the primary
config source. Memory-specific settings live in a "memory" section;
API credentials are resolved from the shared model/backend config
instead of being duplicated.
- Add "memory" section to ~/.config/poc-agent/config.json5
- poc-memory config.rs: try shared config first, fall back to
legacy JSONL
- API fields (base_url, api_key, model) resolved via
memory.agent_model -> models -> backend lookup
- Add json5 dependency for proper JSON5 parsing
- Update provisioning scripts: hermes -> qwen3_coder tool parser
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- distill.agent: fix {{distill}} → {{nodes}} placeholder so seed
nodes actually resolve
- render: show link strength values in the links section, sorted
by strength descending
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config is now stored in RwLock<Arc<Config>> instead of OnceLock<Config>.
get() returns Arc<Config> (cheap clone), and reload() re-reads from disk.
New RPC: "reload-config" — reloads config.jsonl without restarting
the daemon. Logs the change to daemon.log. Useful for switching
between API backends and claude accounts without losing in-flight
tasks.
New CLI: poc-memory agent daemon reload-config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store resource pool in OnceLock so run_job can pass it to
Daemon::run_job for pool state logging. Verbose logging enabled
via POC_MEMORY_VERBOSE=1 env var.
LLM backend selection and spawn-site pool state now use verbose
log level to keep daemon.log clean in production.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from jobkit-daemon crate to jobkit with daemon feature.
Wire up per-task log files for all daemon-spawned agent tasks.
Changes:
- Use jobkit::daemon:: instead of jobkit_daemon::
- All agent tasks get .log_dir() set to $data_dir/logs/
- Task log path shown in daemon status and TUI
- New CLI: poc-memory agent daemon log --task NAME
Finds the task's log path from status or daemon.log, tails the file
- LLM backend selection logged to daemon.log via log_event
- Targeted agent job names include the target key for debuggability
- Logging architecture documented in doc/logging.md
Two-level logging, no duplication:
- daemon.log: lifecycle events with task log path for drill-down
- per-task logs: full agent output via ctx.log_line()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of simulating ratatui's word wrapping algorithm, scan the
rendered buffer to find the actual cursor position. This correctly
handles word wrapping, unicode widths, and any other rendering
nuances that ratatui applies.
The old code computed wrapped_height() and cursor position based on
simple character counting, which diverged from ratatui's WordWrapper
that respects word boundaries.
Now we render first, then walk the buffer counting visible characters
until we reach self.cursor. This is O(area) but the input area is
small (typically < 200 cells), so it's negligible.
Make ApiClient a process-wide singleton via OnceLock so the
connection pool is reused across agent calls. Fix the sync wrapper
to properly pass the caller's log closure through thread::scope
instead of dropping it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run the async API call on a dedicated thread with its own tokio
runtime so it works whether called from a sync context or from
within an existing tokio runtime (daemon).
Also drops the log closure capture issue — uses a simple eprintln
fallback since the closure can't cross thread boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When api_base_url is configured, agents call the LLM directly via
OpenAI-compatible API (vllm, llama.cpp, etc.) instead of shelling
out to claude CLI. Implements the full tool loop: send prompt, if
tool_calls execute them and send results back, repeat until text.
This enables running agents against local/remote models like
Qwen-27B on a RunPod B200, with no dependency on claude CLI.
Config fields: api_base_url, api_key, api_model.
Falls back to claude CLI when api_base_url is not set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lists nodes that are currently deleted with no subsequent live version.
Useful for diagnosing accidental deletions in the memory store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split poc-agent into lib + bin so its API client, types, and tool
dispatch can be imported by poc-memory. This is the foundation for
replacing claude CLI subprocess calls with direct API calls to
vllm/OpenAI-compatible endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move poc-agent (substrate-independent AI agent framework) into the
memory workspace as a step toward using its API client for direct
LLM calls instead of shelling out to claude CLI.
Agent prompt improvements:
- distill: rewrite from hub-focused to knowledge-flow-focused.
Now walks upward from seed nodes to find and refine topic nodes,
instead of only maintaining high-degree hubs.
- distill: remove "don't touch journal entries" restriction
- memory-instructions-core: add "Make it alive" section — write
with creativity and emotional texture, not spreadsheet summaries
- memory-instructions-core: add "Show your reasoning" section —
agents must explain decisions, especially when they do nothing
- linker: already had emotional texture guidance (kept as-is)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Edition 2024 changes:
- gen is reserved: rename variable in query/engine.rs
- set_var is unsafe: wrap in unsafe block in cli/agent.rs
- match ergonomics: add explicit & in spectral.rs filter closure
New --local flag for `poc-memory agent run` bypasses the daemon and
runs the agent directly in-process. Useful for testing agent prompt
changes without waiting in the daemon queue.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The POC_PROVENANCE env var lookup was duplicated in upsert,
delete_node, and rename_node. Extract to a single function.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
delete_node and rename_node were cloning the previous node version
for the tombstone/rename entry without updating provenance or
timestamp. This made it impossible to tell who deleted a node or
when — the tombstone just inherited whatever the last write had.
Now both operations derive provenance from POC_PROVENANCE env var
(same as upsert) and set timestamp to now.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
cmd_history was silently hiding the deleted flag, making it
impossible to tell from the output that a node had been deleted.
This masked the kernel-patterns deletion — looked like the node
existed in the log but wouldn't load.
Also adds merge-logs and diag-key diagnostic binaries, and makes
Node::to_capnp public for use by external tools.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
rewrite_store() used File::create() to truncate and overwrite the
entire nodes.capnp log with only the latest version of each node
from the in-memory store. This destroyed all historical versions
and made no backup. Worse, any node missing from the in-memory
store due to a loading bug would be permanently lost.
strip_md_keys() now appends migrated nodes to the existing log
instead of rewriting it. The dead function is kept with a warning
comment explaining what went wrong.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
calibrate.agent: Haiku-based agent that reads a node and all its
neighbors, then assigns appropriate link strengths relative to each
other. Designed for high-volume runs across the whole graph.
graph link-set: Set strength of an existing link (0.0-1.0).
dominating-set query stage: Greedy 3-covering dominating set — finds
the minimum set of nodes such that every node in the input is within
1 hop of at least 3 selected nodes. Use with calibrate agent to
ensure every link gets assessed from multiple perspectives.
Usage: poc-memory query "content ~ 'bcachefs' | dominating-set"
--target and --query now queue individual daemon tasks instead of
running sequentially in the CLI. Each node gets its own choir task
with LLM resource locking. Falls back to local execution if daemon
isn't running.
RPC extended: "run-agent linker 1 target:KEY" spawns a targeted task.
Run an agent on nodes matching a query:
poc-memory agent run linker --query 'key ~ "bcachefs" | limit 10'
Resolves the query to node keys, then passes all as seeds to the agent.
For large batches, should be queued to daemon (future work).
experience_mine and journal_enrich are replaced by the observation
agent. enrich.rs reduced from 465 to 40 lines — only extract_conversation
and split_on_compaction remain (used by observation fragment selection).
-455 lines.
Remove unused StoreView imports, unused store imports, dead
install_default_file, dead make_report_slug, dead fact-mine/
experience-mine spawning loops in daemon. Fix mut warnings.
Zero compiler warnings now.
All 12 agents with WRITE_NODE/REFINE/END_NODE output format blocks
now rely on tool calls (poc-memory write/link-add/etc) via the
Bash(poc-memory:*) tool. Guidelines preserved, format sections removed.
Also changed linker query from type:episodic to all nodes — it was
missing semantic nodes entirely, which is why skills-bcachefs-* nodes
were never getting linked to their hubs.
Adds run_one_agent_with_keys() which bypasses the agent's query and
uses explicitly provided node keys. This allows testing agents on
specific graph neighborhoods:
poc-memory agent run linker --target bcachefs --debug
New placeholder resolves to the 20 highest-degree nodes, skipping
neighbors of already-selected hubs so the list covers different
regions of the graph. Gives agents a starting point for linking
new content to the right places.
Added to observation.agent prompt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Large conversation segments are now split into 50KB chunks with 10KB
overlap, instead of being truncated to 8000 chars (which was broken
anyway — broke after exceeding, not before). Each chunk gets its own
candidate ID for independent mining and dedup.
format_segment simplified: no size limit, added timestamps to output
so observation agent can cross-reference with journal entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>