SmartMemory
Examples

Decisions Lifecycle

This walkthrough drives a Decision through its full lifecycle using the Python SDK (smartmemory-client):

  1. Create a decision with provenance
  2. Reinforce it as new evidence arrives
  3. Contradict / supersede when reality changes
  4. Retract when it's no longer valid
  5. List, search, retrieve decisions
  6. Inspect provenance and causal chains

Decisions are first-class memory items in SmartMemory — see concepts/decision-memory for the model. This guide focuses on the API surface end-to-end.

Source: smart-memory-service/memory_service/api/routes/decisions.py, smart-memory-client/smartmemory_client/client.py, smart-memory-core/smartmemory/decisions/.

Setup

from smartmemory_client import SmartMemoryClient

client = SmartMemoryClient(
    base_url="http://localhost:9001",
    token="<JWT>",
    team_id="<workspace-uuid>",
)

1. Create a decision

# Optional: store the evidence first so we can attach it
evidence_id = client.add(
    content="Latency p95 measured at 850ms after switching to Postgres pgbouncer.",
    memory_type="observation",
)["item_id"]

decision = client.create_decision(
    content="Use pgbouncer transaction pooling for the billing service.",
    decision_type="choice",        # one of: inference, preference, classification, choice, belief, policy
    confidence=0.85,
    evidence_ids=[evidence_id],
    domain="billing",
    tags=["infra", "pooling"],
    rationale="Reduces connection churn and brings p95 under 200ms.",
    rejected_alternatives=["session pooling", "client-side pooling"],
    constraints=["max_client_conns <= 200"],
)

decision_id = decision["decision_id"]
print(f"created {decision_id} with confidence {decision['confidence']}")

rejected_alternatives, rationale, and constraints come from CORE-EXPERTISE-1 — they capture the expert reasoning around the choice, not just the choice itself.

2. Reinforce with new evidence

When a new observation supports the decision, attach it as evidence. The decision's reinforcement_count ticks up and confidence is recomputed.

new_evidence_id = client.add(
    content="After 2 weeks in prod: p95 ~ 140ms, no connection-pool exhaustion.",
    memory_type="observation",
)["item_id"]

result = client.reinforce_decision(decision_id, evidence_id=new_evidence_id)
print(
    f"reinforced — confidence={result['confidence']}, "
    f"reinforcement_count={result['reinforcement_count']}"
)

3. Contradict — record evidence against

The route POST /memory/decisions/{id}/contradict records contradictory evidence without retracting the decision (it lowers confidence and surfaces in conflict reports). The Python SDK doesn't currently expose a contradict_decision() helper, so call it via the SDK's underlying request method or use HTTP directly:

import httpx

contradict_evidence_id = client.add(
    content="Spike day: pgbouncer transaction pooling broke prepared statements in our ORM.",
    memory_type="observation",
)["item_id"]

# Direct HTTP call until contradict_decision lands in the SDK
httpx.post(
    f"http://localhost:9001/memory/decisions/{decision_id}/contradict",
    headers={"Authorization": "Bearer <JWT>", "X-Team-Id": "<workspace-uuid>"},
    json={"evidence_id": contradict_evidence_id},
).raise_for_status()

4. Supersede — replace with a new decision

When the underlying choice changes, supersede the old decision. This creates a new decision linked to the old via a SUPERSEDES edge — the old one stays in the graph for provenance, marked superseded.

result = client.supersede_decision(
    decision_id,
    new_content="Use pgbouncer session pooling with prepared-statement workaround.",
    reason="Transaction pooling broke ORM prepared statements; session pooling restores them.",
    new_decision_type="choice",
    new_confidence=0.8,
)

new_decision_id = result["new_decision_id"]
print(f"superseded {result['old_decision_id']} -> {new_decision_id}")

5. Retract

If a decision turns out to be entirely wrong (and there's nothing to replace it with), retract it:

client.retract_decision(decision_id, reason="Pooling not needed — load dropped 80%.")

Retracted decisions are filtered out of list_decisions() by default but remain queryable via direct ID lookup.

6. List and retrieve

# List active decisions in a domain
billing_decisions = client.list_decisions(
    domain="billing",
    decision_type="choice",
    min_confidence=0.6,
    limit=20,
)
for d in billing_decisions:
    print(f"{d['decision_id'][:8]}  c={d['confidence']:.2f}  {d['content'][:60]}")

# Retrieve one
d = client.get_decision(new_decision_id)
print(f"status: {d['status']}  domain: {d.get('domain')}  tags: {d.get('tags')}")

The route GET /memory/decisions/search?q=... provides full-text search and is exposed as mcp__smartmemory__decision_search over MCP. Until the Python SDK adds search_decisions(), hit it via httpx like the contradict example above.

7. Provenance and causal chain

# Provenance — the evidence and reasoning behind ONE decision
prov = client.get_provenance_chain(new_decision_id)
print(f"decision: {prov['decision']['content']}")
print(f"reasoning trace: {prov.get('reasoning_trace')}")
print(f"evidence count: {len(prov.get('evidence', []))}")
print(f"superseded: {prov.get('superseded')}")  # links back to old decision

# Causal chain — decisions that caused / were caused by this one
chain = client.get_causal_chain(
    new_decision_id,
    direction="both",   # "upstream" | "downstream" | "both"
    max_depth=3,
)
for hop in chain.get("nodes", []):
    print(f"  {hop['decision_id'][:8]}  {hop['content'][:60]}")

provenance answers "why is this decision the way it is?" — it walks DERIVED_FROM edges to evidence and reasoning traces.

causal-chain answers "what other decisions hang off this?" — it walks SUPERSEDES, CAUSED, and INFORMED_BY edges up to max_depth hops.

End-to-end transcript

Running the full sequence above against a fresh workspace produces something like:

created 7f3a8b2c... with confidence 0.85
reinforced — confidence=0.88, reinforcement_count=1
superseded 7f3a8b2c... -> 9e2d1a44...
status: active  domain: billing  tags: ['infra', 'pooling']
decision: Use pgbouncer session pooling with prepared-statement workaround.
reasoning trace: None
evidence count: 0
superseded: {'old_id': '7f3a8b2c...', 'reason': 'Transaction pooling broke ORM ...'}

On this page