Skip to content

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):

  1. Invariants (always included, even over budget)
  2. Anti-resonance warnings
  3. CodePointers (top-k by task similarity)
  4. Test status summary
  5. Git diff summary
  6. PSS semantic memories
  7. 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:

python -m semvec.coding.mcp_server

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.

python -m semvec.coding.hooks.pre_compact

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.

python -m semvec.coding.hooks.session_start

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.