Coding API (semvec.coding)¶
Context compaction for coding agents. Install with pip install "semvec[coding]" (pulls in fastmcp>=2.0 for the MCP server).
CodingEngine¶
Orchestrator that combines CodePointerIndex, NegativeAttractorSet, and PromptBuilder into a single persistence-backed API.
from semvec.coding import CodingEngine
engine = CodingEngine(
state_dir="~/.semvec/project-x",
embedder=my_embedder, # REQUIRED
workspace_dir=".",
context_budget=6000,
pointer_capacity=100,
attractor_capacity=30,
)
engine.load_state()
engine.register_code_change("auth.py", "JWT validation", "def verify(token)")
engine.record_error("TypeError: None has no attribute 'split'", source="runtime_error")
context = engine.get_compacted_context(
"implement password reset flow",
invariants=["never log plaintext passwords"],
test_summary="12 passed / 0 failed",
git_diff="…",
)
engine.save_state()
Core methods¶
| Method | Purpose |
|---|---|
register_code_change(file_path, intent, signature) |
Add a semantic CodePointer. |
record_error(error_text, source) |
Add a NegativeAttractor. source ∈ {"test_failure", "runtime_error", "user_correction"}. |
check_anti_resonance(proposal, threshold=0.7) |
Returns matching NegativeAttractors above threshold. |
ingest_transcript(path) |
Parse a Claude Code JSONL transcript; returns {conversation_chunks, code_changes, test_failures, errors}. |
get_compacted_context(task, *, invariants, test_summary, git_diff, pss_memories, phase) -> str |
Build the budget-controlled context string. |
save_state() / load_state() |
Persist to / restore from state_dir. |
Explicit embedder required
CodingEngine(state_dir=…) without an embedder raises RuntimeError with a 20-line copy-paste SentenceTransformer wrapper in the message. There is no hash-based fallback — see tests/test_coding_no_fallback.py.
CodePointer / CodePointerIndex¶
from semvec.coding import CodePointer, CodePointerIndex
import numpy as np
idx = CodePointerIndex(capacity=100, w_importance=0.4, w_recency=0.35, w_access=0.25)
idx.register(CodePointer(
intent_vector=np.zeros(384),
file_path="auth.py",
signature="def login()",
importance=1.0,
access_count=0,
))
hits = idx.retrieve(query_vec, top_k=10)
CodePointer exposes intent_vector, file_path, signature, importance, access_count, timestamp, semantic_hash (auto-computed MD5/8 on construction if empty). CodePointerIndex supports register, retrieve, retention_score, plus size / capacity / all_pointers properties and to_dict / from_dict.
NegativeAttractor / NegativeAttractorSet¶
from semvec.coding import NegativeAttractorSet
attractors = NegativeAttractorSet(capacity=30, min_severity=0.01)
attractors.add(error_vec, description="null deref on retry", source="runtime_error", severity=0.9)
warnings = attractors.check(proposal_vec, threshold=0.7)
attractors.decay(factor=0.95) # fade old mistakes
LiteralCache (multi-session structured memory)¶
The Rust-backed LiteralCache (accessed as state.literal_cache) is the structured-memory layer underneath CodingEngine. Use it directly when you want fine-grained control over what survives across sessions: design decisions, recurring error patterns with fixes, hard invariants, per-checkpoint test diffs, and parsed code structures.
import semvec
state = semvec.SemvecState(semvec.SemvecConfig(dimension=768))
cache = state.literal_cache
Recording¶
| Method | What it records |
|---|---|
record_decision(text, checkpoint=0) |
One design decision with timestamp + checkpoint tag |
record_error_pattern(pattern, example, fix, checkpoint=0) |
Recurring error; subsequent calls on the same pattern bump occurrences |
add_invariant(description) / remove_invariant(description) |
Hard rules the agent must not break |
record_test_results(checkpoint, passed_tests, failed_tests, test_groups=None) |
Pass/fail lists for a checkpoint; the cache auto-computes regressions and new_passes against the previous run |
update_code_structures(structures) |
Bulk-load parsed code structures (paths, imports, classes, functions); each function/class also lands as a CodeEntity |
store(entity) |
Add a free-form CodeEntity (any of 14 EntityKinds) |
Reading¶
| Property / method | Returns |
|---|---|
decisions |
list[dict] — {text, checkpoint, timestamp} |
error_patterns |
list[dict] — {pattern, example, fix, occurrences, last_seen_checkpoint} |
invariants |
list[str] |
test_history |
list[dict] — full CheckpointTestSummary per checkpoint |
latest_test_summary |
shortcut for the last test_history entry |
code_structures |
dict[path, structure_dict] |
query(text, max_results=20) |
substring search over key / value / context |
query_by_kind(kind, max_results=20) |
filter by EntityKind |
to_context_block(entities) |
Markdown code-block snippet for a list of entities |
build_handoff_context(next_checkpoint) |
the multi-session-memory headline — Markdown block with INVARIANTS / Test Status / Error Patterns / Decisions, ready to paste into the next session's system prompt |
build_handoff_context() example¶
cache.record_decision("Use mpnet 768d for German content", checkpoint=1)
cache.record_error_pattern(
pattern="catastrophic recency bias on blocked-domain ingest",
example="500-note 4-domain blocked sequence",
fix="raise long_term_size and use tier weights 1.0/0.95/0.9",
checkpoint=1,
)
cache.add_invariant("State must round-trip via to_dict/from_dict")
cache.record_test_results(checkpoint=1, passed_tests=["test_a", "test_b", "test_c"], failed_tests=[])
print(cache.build_handoff_context(next_checkpoint=2))
### INVARIANTS — Do NOT break these:
- State must round-trip via to_dict/from_dict
### Test Status (CP1: 100%, 3/3)
### Known Error Patterns
- `catastrophic recency bias on blocked-domain ingest` (x1): raise long_term_size and use tier weights 1.0/0.95/0.9
### Design Decisions
- [CP1] Use mpnet 768d for German content
Round-trip — what survives to_dict() / to_bytes()¶
Since 0.3.1, the round-trip preserves the full structural state: entities, decisions, error_patterns, invariants, test_history, code_structures. Original timestamps and occurrences counters are kept exactly as written (the bulk-restore path bypasses record_*'s re-stamping). Pre-0.3.1 these fields were silently dropped on restore — build_handoff_context() returned an empty string after a session restart.
# session 1: record everything
state.literal_cache.record_decision(...)
blob = state.to_bytes()
# session 2: pick up where the agent left off
restored = semvec.SemvecState.from_bytes(blob)
assert restored.literal_cache.build_handoff_context(2) == ctx
PromptBuilder¶
from semvec.coding import PromptBuilder
builder = PromptBuilder(budget=6000)
ctx = builder.build(
code_pointers=idx,
negative_attractors=attractors,
task_vector=task_vec,
invariants=["python ≥ 3.10"],
test_summary="12 passed / 0 failed",
phase="stability",
)
Section priority (always included first → dropped last when budget is tight):
- Invariants (always included, even over budget)
- Anti-resonance warnings
- CodePointers (top-k by task similarity)
- Test status summary
- Git diff summary
- PSS semantic memories
- Phase indicator
TranscriptParser / TranscriptChunk / ChunkType¶
Parses Claude Code JSONL transcripts into semantically-typed chunks.
from semvec.coding import TranscriptParser, ChunkType
chunks = TranscriptParser().parse("session.jsonl")
for c in chunks:
if c.chunk_type == ChunkType.CODE_CHANGE:
...
ChunkType members: CONVERSATION, CODE_CHANGE, TEST_FAILURE, ERROR.
CompactionState + save_state / load_state¶
Low-level persistence format used internally by CodingEngine.save_state / load_state. Expose your own persistence by calling these directly.
MCP server (semvec.coding.mcp_server)¶
Exposes six tools over FastMCP stdio:
| Tool | Arguments | Returns |
|---|---|---|
pss_get_context |
task, invariants=None, test_summary=None |
compact context string |
pss_update |
text, update_type="conversation", file_path=None, signature=None |
status string |
pss_check_anti_resonance |
proposal |
"Clear" or warning list |
pss_register_code |
file_path, intent, signature |
status |
pss_record_error |
error_text, source="runtime_error" |
status |
pss_save |
— | saved-to-path string |
Run directly:
Or wire into .claude/settings.json — see Quickstart §6.
Environment variables read at start-up: SEMVEC_STATE_DIR, SEMVEC_WORKSPACE, SEMVEC_EMBED_MODEL, SEMVEC_EMBED_DEVICE.
Testable handler surface¶
Tool logic lives in pure functions so unit tests can bypass FastMCP entirely:
from semvec.coding.mcp_server import (
create_engine,
handle_get_context,
handle_update,
handle_check_anti_resonance,
handle_register_code,
handle_record_error,
handle_save,
)
Claude Code hooks (semvec.coding.hooks)¶
pre_compact¶
Fires before context compaction. Ingests the transcript, persists state, generates compacted context.
Reads JSON from stdin:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/workspace",
"trigger": "auto"
}
Writes a result dict to stderr (Claude Code reads this) and passes the original stdin through on stdout.
session_start¶
Fires when a new session begins. Loads prior state and reports pointer / attractor counts.
Both hooks accept an injected embedder via handle_pre_compact(hook_input, state_dir=None, embedder=None) / handle_session_start(...). Missing embedder falls back to constructing a SentenceTransformer from SEMVEC_EMBED_MODEL / SEMVEC_EMBED_DEVICE — fails loudly if sentence-transformers is not installed.