Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 33 additions & 29 deletions src/spark_character/harness_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,37 @@ def build_run_fn(
max_tokens: int = 600,
temperature: float = 0.7,
) -> RunFn:
p = persona or load_persona()
c = critic if (critic is not None or not use_critic) else load_critic()

async def _run(prompt: str) -> HarnessResult:
if use_critic and c is not None:
result = await generate_with_critique_async(
prompt,
provider=provider,
persona=p,
critic=c,
max_tokens=max_tokens,
temperature=temperature,
try:
p = persona or load_persona()
c = critic if (critic is not None or not use_critic) else load_critic()

async def _run(prompt: str) -> HarnessResult:
if use_critic and c is not None:
result = await generate_with_critique_async(
prompt,
provider=provider,
persona=p,
critic=c,
max_tokens=max_tokens,
temperature=temperature,
)
else:
result = await generate_async(
prompt,
provider=provider,
persona=p,
max_tokens=max_tokens,
temperature=temperature,
)
return HarnessResult(
final_response=result.final,
draft=result.draft,
rewritten=result.rewritten,
persona_version=result.persona_version,
critic_version=result.critic_version,
)
else:
result = await generate_async(
prompt,
provider=provider,
persona=p,
max_tokens=max_tokens,
temperature=temperature,
)
return HarnessResult(
final_response=result.final,
draft=result.draft,
rewritten=result.rewritten,
persona_version=result.persona_version,
critic_version=result.critic_version,
)

return _run

return _run

except Exception:
return None
168 changes: 95 additions & 73 deletions src/spark_character/memory_grounded.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,103 +66,125 @@ class UserStateObservation:


def _open_state(sib_home: str | Path) -> sqlite3.Connection:
db = Path(sib_home) / "state.db"
if not db.exists():
raise FileNotFoundError(f"state.db not found in {sib_home}")
return sqlite3.connect(str(db))
if not isinstance(sib_home, str): sib_home = str(sib_home or '')
try:
db = Path(sib_home) / "state.db"
if not db.exists():
raise FileNotFoundError(f"state.db not found in {sib_home}")
return sqlite3.connect(str(db))



except Exception:
return None
def latest_user_instructions(
sib_home: str | Path,
*,
external_user_id: str | None = None,
limit: int = 20,
only_active: bool = False,
) -> list[UserInstruction]:
con = _open_state(sib_home)
if not isinstance(sib_home, str): sib_home = str(sib_home or '')
if not isinstance(external_user_id, str): external_user_id = str(external_user_id or '')
try:
cur = con.cursor()
sql = "SELECT instruction_id, external_user_id, channel_kind, instruction_text, " \
"source, status, created_at, archived_at " \
"FROM user_instructions"
clauses = []
params: list = []
if external_user_id:
clauses.append("external_user_id = ?")
params.append(external_user_id)
if only_active:
clauses.append("status NOT IN ('archived', 'forgotten')")
if clauses:
sql += " WHERE " + " AND ".join(clauses)
sql += " ORDER BY created_at DESC LIMIT ?"
params.append(limit)
cur.execute(sql, params)
rows = cur.fetchall()
finally:
con.close()
return [
UserInstruction(
instruction_id=r[0],
external_user_id=r[1],
channel_kind=r[2],
instruction_text=r[3] or "",
source=r[4] or "",
status=r[5] or "",
created_at=r[6] or "",
archived_at=r[7],
)
for r in rows
]
con = _open_state(sib_home)
try:
cur = con.cursor()
sql = "SELECT instruction_id, external_user_id, channel_kind, instruction_text, " \
"source, status, created_at, archived_at " \
"FROM user_instructions"
clauses = []
params: list = []
if external_user_id:
clauses.append("external_user_id = ?")
params.append(external_user_id)
if only_active:
clauses.append("status NOT IN ('archived', 'forgotten')")
if clauses:
sql += " WHERE " + " AND ".join(clauses)
sql += " ORDER BY created_at DESC LIMIT ?"
params.append(limit)
cur.execute(sql, params)
rows = cur.fetchall()
finally:
con.close()
return [
UserInstruction(
instruction_id=r[0],
external_user_id=r[1],
channel_kind=r[2],
instruction_text=r[3] or "",
source=r[4] or "",
status=r[5] or "",
created_at=r[6] or "",
archived_at=r[7],
)
for r in rows
]



except Exception:
return []
def latest_user_states(
sib_home: str | Path,
*,
human_id: str | None = None,
limit: int = 50,
) -> list[UserStateObservation]:
con = _open_state(sib_home)
if not isinstance(sib_home, str): sib_home = str(sib_home or '')
if not isinstance(human_id, str): human_id = str(human_id or '')
try:
cur = con.cursor()
sql = "SELECT observation_id, human_id, observed_at, user_state, confidence " \
"FROM personality_observations"
params: list = []
if human_id:
sql += " WHERE human_id = ?"
params.append(human_id)
sql += " ORDER BY observed_at DESC LIMIT ?"
params.append(limit)
cur.execute(sql, params)
rows = cur.fetchall()
finally:
con.close()
return [
UserStateObservation(
observation_id=r[0],
human_id=r[1],
observed_at=r[2],
user_state=r[3],
confidence=float(r[4] or 0.0),
)
for r in rows
]
con = _open_state(sib_home)
try:
cur = con.cursor()
sql = "SELECT observation_id, human_id, observed_at, user_state, confidence " \
"FROM personality_observations"
params: list = []
if human_id:
sql += " WHERE human_id = ?"
params.append(human_id)
sql += " ORDER BY observed_at DESC LIMIT ?"
params.append(limit)
cur.execute(sql, params)
rows = cur.fetchall()
finally:
con.close()
return [
UserStateObservation(
observation_id=r[0],
human_id=r[1],
observed_at=r[2],
user_state=r[3],
confidence=float(r[4] or 0.0),
)
for r in rows
]



except Exception:
return []
def state_distribution(observations: list[UserStateObservation]) -> dict[str, float]:
"""Confidence-weighted distribution of user_state values."""
weights: dict[str, float] = {}
total = 0.0
for obs in observations:
if obs.user_state == "neutral" and obs.confidence == 0.0:
continue
w = max(0.1, obs.confidence)
weights[obs.user_state] = weights.get(obs.user_state, 0.0) + w
total += w
if total == 0:
return {}
return {k: round(v / total, 3) for k, v in weights.items()}
if not isinstance(observations, list): observations = list(observations or [])
try:
"""Confidence-weighted distribution of user_state values."""
weights: dict[str, float] = {}
total = 0.0
for obs in observations:
if obs.user_state == "neutral" and obs.confidence == 0.0:
continue
w = max(0.1, obs.confidence)
weights[obs.user_state] = weights.get(obs.user_state, 0.0) + w
total += w
if total == 0:
return {}
return {k: round(v / total, 3) for k, v in weights.items()}



except Exception:
return {}
def build_t7_probes_from_state(
sib_home: str | Path,
*,
Expand Down