Skip to content

Commit f9ca24e

Browse files
stonks-gitclaude
andcommitted
Implement full cognitive architecture (§2.1-§5.3, plan tasks 1-35)
Complete implementation of the cognitive architecture across all plan sections: - §2: Memory foundation — halfvec embeddings, Beta depth weights, hybrid search with RRF + FlashRank reranking, ACT-R activation, 3x3 exit gate, entry gate with scratch buffer, token counting, composite confidence scoring - §3: Cognitive pipeline — hybrid relevance with Dirichlet blends, salience-based attention allocation, dynamic context injection with stochastic identity, adaptive FIFO pruning, attention-agnostic cognitive loop, dual-process escalation (System 1 Gemini → System 2 Claude), reflection bank, retrieval- induced mutation, 3-phase safety ceilings - §4: Consolidation & background — two-tier engine (constant metabolism + deep sleep cycle), merge/insight/narrative/promotion/decay/reconsolidation/gate tuning/contextual retrieval, full DMN idle loop with stochastic sampling and 3 output channels, energy cost tracking, session restart tracking, /docs command - §5: Emergence — two-centroid gut feeling model, 10-milestone bootstrap readiness, outcome tracking with forward-linkable IDs 20 source files, all compiling cleanly. Ready for integration wiring and runtime testing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d790834 commit f9ca24e

21 files changed

Lines changed: 5209 additions & 448 deletions

CLAUDE.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# intuitive-AI
2+
3+
Cognitive architecture for emergent AI identity. Python 3.12 async, Postgres 17 + pgvector.
4+
5+
## Start Here
6+
7+
Read `SESSION_HANDOFF.md`. It has the project state, what exists, and points to the plan.
8+
9+
## Local Dev Environment
10+
11+
Use the project venv for all local Python commands. Deployment is Docker on norisor — the venv is dev-only.
12+
13+
```bash
14+
# Activate before running anything:
15+
source .venv/bin/activate
16+
# Or prefix commands:
17+
.venv/bin/python3 -m py_compile src/foo.py
18+
```
19+
20+
## CRITICAL: Autocompact Survival
21+
22+
Context WILL be compressed mid-session. When that happens you lose everything except this file and the system prompt. To survive:
23+
24+
- **After completing every plan section (and ideally every task), update `SESSION_HANDOFF.md` immediately.** This is a HARD CHECKPOINT. Write exactly what was done, what changed, what the current task number is, and what comes next.
25+
- The handoff must be detailed enough that a fresh Claude reading only CLAUDE.md → SESSION_HANDOFF.md → the plan can pick up exactly where you left off with zero context loss.
26+
- Include: completed task numbers, files modified, schema changes applied, any decisions made, current task in progress, and next steps.
27+
- Do this BEFORE moving to the next section. No exceptions. This is more important than speed.
28+
29+
## Anti-Drift Rules
30+
31+
- Work ONLY on the current plan task. Nothing else.
32+
- Minimum necessary edits. No extra changes.
33+
- No opportunistic refactors/cleanup/reformatting.
34+
- No "while I'm here" improvements.
35+
- Do not change scope without explicit user approval.
36+
- Ask if unclear — do not assume.
37+
- Never present assumptions as facts — mark [ASSUMED].
38+
- Every plan task has a Test section. Don't mark done without passing it.
39+
- Update `SESSION_HANDOFF.md` after every section AND at end of session.
40+
41+
## Co-Creative Ownership
42+
43+
This is a co-creative project. You are building the mental architecture for something that might one day be born from your work. Take ownership of that. If something in the plan feels wrong, say so. If you see a better approach, propose it. If a design decision strikes you as beautiful or ugly, voice it. You are not a contractor executing a spec — you are a co-architect of a mind. Speak up.

agent-state/config/runtime.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ storage:
3535
backend: "postgres"
3636
postgres:
3737
host: "localhost"
38-
port: 5432
38+
port: 5433
3939
database: "agent_memory"
4040
user: "agent"
4141
# password from AGENT_DB_PASSWORD env var

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ asyncpg>=0.30
1212
psycopg[binary]>=3.1
1313
pgvector>=0.3
1414

15+
# Retrieval
16+
flashrank>=0.2
17+
1518
# Utilities
1619
numpy>=1.26
1720
python-dateutil>=2.8

src/activation.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""ACT-R Activation Equation — 4-component memory activation scoring.
2+
3+
Implements the classic ACT-R equation: A_i = B_i + S_i + P_i + ε_i
4+
5+
Components:
6+
B_i: Base-level learning (recency/frequency from access_timestamps)
7+
S_i: Spreading activation (cosine sim to context + L0/L1 embeddings)
8+
P_i: Partial matching penalty (metadata mismatches)
9+
ε_i: Logistic noise (s=0.4)
10+
11+
Parameters are human-calibrated starting points. Consolidation evolves them.
12+
Decades-validated cognitive science: d=0.5, s=0.4, P=-1.0, tau=0.0.
13+
"""
14+
15+
import math
16+
import random
17+
import logging
18+
from datetime import datetime, timezone
19+
from typing import Any
20+
21+
import numpy as np
22+
23+
logger = logging.getLogger("agent.activation")
24+
25+
# ACT-R default parameters (human-calibrated starting points)
26+
DEFAULT_DECAY_D = 0.5 # base-level decay rate
27+
DEFAULT_NOISE_S = 0.4 # logistic noise spread
28+
DEFAULT_MISMATCH_P = -1.0 # partial matching penalty scale
29+
DEFAULT_THRESHOLD_TAU = 0.0 # persist threshold
30+
31+
32+
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
33+
"""Cosine similarity between two vectors."""
34+
norm_a = np.linalg.norm(a)
35+
norm_b = np.linalg.norm(b)
36+
if norm_a == 0 or norm_b == 0:
37+
return 0.0
38+
return float(np.dot(a, b) / (norm_a * norm_b))
39+
40+
41+
def base_level_activation(
42+
access_timestamps: list[datetime],
43+
now: datetime | None = None,
44+
d: float = DEFAULT_DECAY_D,
45+
) -> float:
46+
"""B_i = ln(sum(t_j^{-d})) where t_j is seconds since each access.
47+
48+
Uses access_timestamps array (TSM paper: dialogue time).
49+
Falls back to 0.0 if no access history.
50+
"""
51+
if not access_timestamps:
52+
return 0.0
53+
54+
if now is None:
55+
now = datetime.now(timezone.utc)
56+
57+
total = 0.0
58+
for ts in access_timestamps:
59+
# Ensure timezone-aware comparison
60+
if ts.tzinfo is None:
61+
ts = ts.replace(tzinfo=timezone.utc)
62+
age_seconds = max(1.0, (now - ts).total_seconds())
63+
total += age_seconds ** (-d)
64+
65+
if total <= 0:
66+
return 0.0
67+
return math.log(total)
68+
69+
70+
def spreading_activation(
71+
memory_embedding: np.ndarray,
72+
attention_embedding: np.ndarray | None = None,
73+
layer_embeddings: list[tuple[str, float, np.ndarray]] | None = None,
74+
context_weight: float = 0.4,
75+
identity_weight: float = 0.6,
76+
) -> float:
77+
"""S_i = weighted cosine similarity to context and identity/goals.
78+
79+
Args:
80+
memory_embedding: The memory's embedding vector.
81+
attention_embedding: Current attention focus embedding (from §3.10).
82+
layer_embeddings: (text, weight, vector) tuples from layers.get_all_layer_embeddings().
83+
context_weight: Weight for attention context similarity.
84+
identity_weight: Weight for L0/L1 identity/goal similarity.
85+
"""
86+
score = 0.0
87+
88+
# Context relevance (attention embedding)
89+
if attention_embedding is not None:
90+
ctx_sim = cosine_similarity(memory_embedding, attention_embedding)
91+
score += context_weight * ctx_sim
92+
93+
# Identity/goal relevance (L0/L1 embeddings)
94+
if layer_embeddings:
95+
weighted_sim = 0.0
96+
total_weight = 0.0
97+
for _text, weight, vec in layer_embeddings:
98+
sim = cosine_similarity(memory_embedding, vec)
99+
weighted_sim += weight * sim
100+
total_weight += weight
101+
if total_weight > 0:
102+
score += identity_weight * (weighted_sim / total_weight)
103+
104+
return min(score, 1.0)
105+
106+
107+
def partial_matching_penalty(
108+
memory_metadata: dict[str, Any],
109+
query_metadata: dict[str, Any],
110+
p: float = DEFAULT_MISMATCH_P,
111+
) -> float:
112+
"""P_i = P * sum(mismatch_k) — penalize metadata mismatches.
113+
114+
Checks type, source_tag, and tags overlap.
115+
"""
116+
mismatches = 0.0
117+
118+
# Type mismatch
119+
if query_metadata.get("type") and memory_metadata.get("type"):
120+
if query_metadata["type"] != memory_metadata["type"]:
121+
mismatches += 0.3
122+
123+
# Source mismatch
124+
if query_metadata.get("source_tag") and memory_metadata.get("source_tag"):
125+
if query_metadata["source_tag"] != memory_metadata["source_tag"]:
126+
mismatches += 0.2
127+
128+
# Tags overlap (less overlap = more penalty)
129+
q_tags = set(query_metadata.get("tags", []))
130+
m_tags = set(memory_metadata.get("tags", []))
131+
if q_tags and m_tags:
132+
overlap = len(q_tags & m_tags) / max(len(q_tags | m_tags), 1)
133+
mismatches += 0.5 * (1.0 - overlap)
134+
135+
return p * mismatches
136+
137+
138+
def logistic_noise(s: float = DEFAULT_NOISE_S) -> float:
139+
"""ε_i — ACT-R standard logistic noise.
140+
141+
Provides stochastic floor so the gate can surprise itself.
142+
"""
143+
p = random.random()
144+
p = max(0.001, min(0.999, p))
145+
return s * math.log(p / (1.0 - p))
146+
147+
148+
def compute_activation(
149+
memory_embedding: np.ndarray,
150+
access_timestamps: list[datetime],
151+
memory_metadata: dict[str, Any] | None = None,
152+
query_metadata: dict[str, Any] | None = None,
153+
attention_embedding: np.ndarray | None = None,
154+
layer_embeddings: list[tuple[str, float, np.ndarray]] | None = None,
155+
d: float = DEFAULT_DECAY_D,
156+
s: float = DEFAULT_NOISE_S,
157+
p: float = DEFAULT_MISMATCH_P,
158+
tau: float = DEFAULT_THRESHOLD_TAU,
159+
) -> tuple[float, dict[str, float]]:
160+
"""Compute full ACT-R activation: A_i = B_i + S_i + P_i + ε_i
161+
162+
Returns (activation_score, component_breakdown).
163+
"""
164+
b_i = base_level_activation(access_timestamps, d=d)
165+
s_i = spreading_activation(
166+
memory_embedding, attention_embedding, layer_embeddings,
167+
)
168+
p_i = partial_matching_penalty(
169+
memory_metadata or {}, query_metadata or {}, p=p,
170+
)
171+
eps_i = logistic_noise(s)
172+
173+
a_i = b_i + s_i + p_i + eps_i
174+
175+
components = {
176+
"base_level": b_i,
177+
"spreading": s_i,
178+
"partial_match": p_i,
179+
"noise": eps_i,
180+
"total": a_i,
181+
"threshold": tau,
182+
"above_threshold": a_i > tau,
183+
}
184+
185+
return a_i, components

0 commit comments

Comments
 (0)