-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathidentity.py
More file actions
110 lines (97 loc) · 5.42 KB
/
Copy pathidentity.py
File metadata and controls
110 lines (97 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""The agent's self-model: static facts about what Tuffy IS, owned entirely
by code and the active model card. This is deliberately NOT part of
long-term memory (data/memory/) — an LLM reflection pass must never write
here, because a small model cannot reliably tell "a fact about the user" from
"a fact I just said about myself" (it kept storing its own model name/role/
purpose into the user's profile). Identity is fixed; memory is learned.
src/prompts/templates.py's self_model() renders this into the system prompt.
"""
AGENT_NAME = "Tuffy"
AGENT_TAGLINE = "a tool-using AI agent"
# Keys that must never be written into user memory by the automatic fact
# extractor (elastimem's background extraction, passed in as reserved_keys
# when src/memory.py opens the store) — these describe the agent itself,
# not the user, no matter what phrasing they show up under.
#
# Deliberately excludes bare "name"/"role"/"title": those are legitimate
# facts ABOUT THE USER too (the user's own name, job title, etc.), so a blanket
# ban would reject real profile data. The agent/assistant/ai/model-prefixed
# and framework/hardware-specific variants are unambiguous, so those are safe
# to always reject.
RESERVED_IDENTITY_KEYS = {
"model", "hardware", "framework", "capabilities", "llm", "identity",
"who_i_am", "agent_name", "agent_role", "agent_purpose", "agent_model",
"assistant_name", "assistant_role", "assistant_model",
"ai_name", "ai_model", "ai_role",
}
# Keys that are conversation-transcript shaped rather than fact shaped — a
# small extractor model sometimes echoes the raw exchange back as if the
# transcript itself were a "fact" ({"user_message": "...", "assistant_reply":
# "..."}). These describe an EXCHANGE, not a durable fact about the user, so
# they're rejected regardless of value.
_TRANSCRIPT_KEY_MARKERS = (
"user", "assistant", "message", "reply", "replied", "response",
"responded", "said", "says", "conversation", "exchange", "dialogue",
"utterance",
)
def is_transcript_key(normalized_key: str) -> bool:
parts = normalized_key.split("_")
return any(part in _TRANSCRIPT_KEY_MARKERS for part in parts)
# Substrings that mark a VALUE as describing the agent rather than the user,
# regardless of which key it's filed under (a small model files "I'm a local
# AI agent" under plain "role", "purpose", "title" just as often as under an
# agent_-prefixed key). Deliberately provider-agnostic — these must keep
# matching regardless of which model or backend is currently loaded.
_SELF_REFERENTIAL_VALUE_MARKERS = (
"i'm tuffy", "i am tuffy", "local ai agent", "ai agent", "language model",
"i run on", "running locally", "large language model",
)
def is_self_referential_value(value: str) -> bool:
lowered = value.lower()
return any(marker in lowered for marker in _SELF_REFERENTIAL_VALUE_MARKERS)
# Human-readable label per provider, for the self-model description below.
# New providers just need an entry here — everything else reads generically
# off the model card.
_PROVIDER_LABELS = {
"llama_cpp": "running locally",
"openai_compatible": "via API",
"anthropic": "via Anthropic API",
}
def describe(model_card: dict, network_mode: str = "online", voice_mode: bool = False) -> str:
"""One rendered block describing the agent for the system prompt. Reads
everything from the active model card so this never hardcodes a specific
model family or backend — swapping models or providers needs no change
here. network_mode and voice_mode are passed in explicitly (rather than
imported) so the live session's actual state and the model's
self-description can never drift apart."""
caps = ", ".join(model_card["capabilities"])
provider_label = _PROVIDER_LABELS.get(model_card["provider"], model_card["provider"])
quant = f", {model_card['quantization']} quant" if model_card.get("quantization") not in (None, "none") else ""
params = f", {model_card['parameters']} params" if model_card.get("parameters") else ""
mode_note = (
"no internet access — web_search and translate are unavailable"
if network_mode == "offline"
else "internet access available"
)
interaction_note = (
"- Interaction mode: voice — the user is speaking to you and your reply will be "
"read aloud by text-to-speech. Answer the way a person would on a phone call: "
"plain spoken sentences, one thought flowing into the next. Never use markdown, "
"bullet points, numbered lists, headers, bold/italic markers, or code blocks — "
"none of that can be heard, and a list read aloud sounds robotic. If you'd "
"normally give a list, just say the items in a sentence with 'and'/'then' "
"instead of dashes or numbers.\n"
if voice_mode
else "- Interaction mode: text — the user is reading your reply on screen, so normal "
"formatting (lists, code blocks, headers) is fine when it helps.\n"
)
return (
f"- You are {AGENT_NAME}, {AGENT_TAGLINE}.\n"
f"- Currently running on: {model_card['name']} "
f"({model_card['family']}{params}{quant}, capabilities: {caps}), {provider_label}.\n"
f"- Network mode: {network_mode} ({mode_note}).\n"
f"{interaction_note}"
"- This identity is fixed by your configuration, not something you or the user "
"can 'remember' or change — never store your own name, model, or role as a "
"fact about the user."
)