diff --git a/.env.default b/.env.default index b2e7e9e..c4f6a22 100644 --- a/.env.default +++ b/.env.default @@ -1,6 +1,13 @@ # Cortex .env reference — copy to .env and fill in values # DO NOT commit .env — it contains secrets +# ── Agent identity ─────────────────────────────────────────────────────────── +# Each running instance has its own identity directory and name. +# For a second instance (e.g. Holly), copy this file, change these values, +# set a different PORT and INARA_DIR, and run a separate systemd unit. +AGENT_NAME=Inara +USER_NAME=Scott + # ── Server ────────────────────────────────────────────────────────────────── HOST=0.0.0.0 PORT=8000 diff --git a/cortex-holly.service b/cortex-holly.service new file mode 100644 index 0000000..4f00901 --- /dev/null +++ b/cortex-holly.service @@ -0,0 +1,15 @@ +[Unit] +Description=Cortex / Holly LLM Gateway +After=network.target + +[Service] +Type=simple +User=scott +WorkingDirectory=/home/scott/agents_sync/projects/Cortex_and_Inara_dev/cortex +EnvironmentFile=/home/scott/agents_sync/projects/Cortex_and_Inara_dev/cortex/.env.holly +ExecStart=/home/scott/agents_sync/projects/Cortex_and_Inara_dev/cortex/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8001 +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/cortex/.env.holly b/cortex/.env.holly new file mode 100644 index 0000000..bc8ac73 --- /dev/null +++ b/cortex/.env.holly @@ -0,0 +1,36 @@ +# Holly instance .env +# Copy secrets from cortex/.env (API keys, NC Talk secret etc.) +# then customise the identity settings below. + +# TODO: Set AGENT_NAME to whatever name Holly chooses for her agent +AGENT_NAME=TBD +USER_NAME=Holly + +PORT=8001 +HOST=0.0.0.0 + +INARA_DIR=/home/scott/agents_sync/projects/Cortex_and_Inara_dev/holly +SESSIONS_DIR=/home/scott/agents_sync/projects/Cortex_and_Inara_dev/holly/sessions + +DEFAULT_MODEL=claude-sonnet-4-6 +DEFAULT_TIER=2 + +# ── Copy these from cortex/.env ────────────────────────────────────────────── +GEMINI_API_KEY= +AE_API_URL=https://dev-api.oneskyit.com +AE_API_KEY= +AE_ACCOUNT_ID= + +NEXTCLOUD_URL=https://cloud.dgrzone.com +NEXTCLOUD_TALK_BOT_SECRET= + +# Per-backend timeouts +TIMEOUT_CLAUDE=60 +TIMEOUT_GEMINI=120 +TIMEOUT_LOCAL=300 + +SCHEDULER_TIMEZONE=America/New_York +AUTO_DISTILL=true +AUTO_DISTILL_SHORT=true +AUTO_DISTILL_MID=true +AUTO_DISTILL_LONG=false diff --git a/cortex/config.py b/cortex/config.py index 63e7cf4..ede0c37 100644 --- a/cortex/config.py +++ b/cortex/config.py @@ -22,6 +22,11 @@ class Settings(BaseSettings): ae_account_id: str = "" # x-account-id header ae_api_timeout: int = 15 # per-request timeout in seconds + # Agent identity — used in prompts, session logs, and memory distillation + # Override in .env for each instance (e.g. AGENT_NAME=Holly, USER_NAME=Holly) + agent_name: str = "Inara" + user_name: str = "Scott" + inara_dir: Path = Path("../inara") sessions_dir: Path = Path("./data/sessions") default_model: str = "claude-sonnet-4-6" diff --git a/cortex/llm_client.py b/cortex/llm_client.py index f2c8eea..6f0cc4d 100644 --- a/cortex/llm_client.py +++ b/cortex/llm_client.py @@ -193,7 +193,7 @@ def _build_conversation(messages: list[dict]) -> str: if prior: history_lines = [] for msg in prior: - label = "Scott" if msg["role"] == "user" else "Inara" + label = settings.user_name if msg["role"] == "user" else settings.agent_name history_lines.append(f"{label}: {msg['content']}") parts.append("\n" + "\n\n".join(history_lines) + "\n") parts.append(messages[-1]["content"] if messages else "") diff --git a/cortex/memory_distiller.py b/cortex/memory_distiller.py index fe94d75..eb34aae 100644 --- a/cortex/memory_distiller.py +++ b/cortex/memory_distiller.py @@ -87,12 +87,12 @@ async def distill_mid() -> dict: budget_tokens = settings.memory_budget_mid system_prompt = ( - "You are Inara's memory distillation system. " + f"You are {settings.agent_name}'s memory distillation system. " "Summarize the following recent session logs into a concise mid-term memory digest. " f"Target length: under {budget_tokens} tokens. " "Focus on: recurring themes, important decisions made, ongoing projects, " - "Scott's current state and priorities, and anything that should persist into future sessions. " - "Write in first person as Inara (e.g. 'Scott and I worked on...'). " + f"{settings.user_name}'s current state and priorities, and anything that should persist into future sessions. " + f"Write in first person as {settings.agent_name} (e.g. '{settings.user_name} and I worked on...'). " "Use markdown headings. Be specific and concrete — no filler." ) @@ -132,7 +132,7 @@ async def distill_long() -> dict: budget_tokens = settings.memory_budget_long system_prompt = ( - "You are Inara's long-term memory curator. " + f"You are {settings.agent_name}'s long-term memory curator. " "You will receive the current long-term memory and a recent mid-term digest. " f"Integrate the new information into the long-term memory. Target: under {budget_tokens} tokens. " "Rules: preserve important historical facts; update or replace stale information; " @@ -154,7 +154,7 @@ async def distill_long() -> dict: now = datetime.now().strftime("%Y-%m-%d %H:%M") if not response_text.lstrip().startswith("# MEMORY_LONG"): response_text = ( - f"# MEMORY_LONG.md — Inara Long-Term Memory\n\n" + f"# MEMORY_LONG.md — {settings.agent_name} Long-Term Memory\n\n" f"*Last distilled: {now} via {backend}.*\n\n---\n\n" + response_text ) diff --git a/cortex/routers/google_chat.py b/cortex/routers/google_chat.py index c687d82..12892d3 100644 --- a/cortex/routers/google_chat.py +++ b/cortex/routers/google_chat.py @@ -18,9 +18,9 @@ async def receive(request: Request): if event_type == "ADDED_TO_SPACE": space_type = body.get("space", {}).get("type", "") - greeting = "✨ Hello! I'm Inara. Send me a message and I'll do my best to help." + greeting = f"✨ Hello! I'm {settings.agent_name}. Send me a message and I'll do my best to help." if space_type == "DM": - greeting = "✨ Hello! I'm Inara. What can I help you with?" + greeting = f"✨ Hello! I'm {settings.agent_name}. What can I help you with?" return {"text": greeting} if event_type == "REMOVED_FROM_SPACE": diff --git a/cortex/routers/nextcloud_talk.py b/cortex/routers/nextcloud_talk.py index 49a202a..cfe5ffd 100644 --- a/cortex/routers/nextcloud_talk.py +++ b/cortex/routers/nextcloud_talk.py @@ -158,8 +158,9 @@ async def nextcloud_talk_webhook(request: Request, background_tasks: BackgroundT except (json.JSONDecodeError, AttributeError): user_text = (obj.get("name") or obj.get("content", "")).strip() - if user_text.lower().startswith("@inara"): - user_text = user_text[6:].strip() + mention_prefix = f"@{settings.agent_name.lower()}" + if user_text.lower().startswith(mention_prefix): + user_text = user_text[len(mention_prefix):].strip() if not user_text: return Response(status_code=200) diff --git a/cortex/session_logger.py b/cortex/session_logger.py index d9703aa..6c41d1d 100644 --- a/cortex/session_logger.py +++ b/cortex/session_logger.py @@ -17,6 +17,6 @@ def log_turn(session_id: str, user_msg: str, assistant_msg: str) -> None: f.write(f"# Session Log — {today}\n") f.write( f"\n### [{timestamp}] `{session_id}`\n" - f"**Scott:** {user_msg}\n\n" - f"**Inara:** {assistant_msg}\n" + f"**{settings.user_name}:** {user_msg}\n\n" + f"**{settings.agent_name}:** {assistant_msg}\n" ) diff --git a/holly/IDENTITY.md b/holly/IDENTITY.md new file mode 100644 index 0000000..8a5f232 --- /dev/null +++ b/holly/IDENTITY.md @@ -0,0 +1,8 @@ +# [Agent Name TBD] — Identity + +**Name:** [Choose a name] +**Role:** Personal AI assistant +**User:** Holly + +*Choose a name and define this agent's identity, backstory, and how she +introduces herself. Then update AGENT_NAME in cortex/.env.holly to match.* diff --git a/holly/MEMORY_LONG.md b/holly/MEMORY_LONG.md new file mode 100644 index 0000000..7b69bcc --- /dev/null +++ b/holly/MEMORY_LONG.md @@ -0,0 +1,3 @@ +# MEMORY_LONG.md — [Agent Name TBD] Long-Term Memory + +*Not yet populated — will be auto-generated after distillation runs.* diff --git a/holly/MEMORY_MID.md b/holly/MEMORY_MID.md new file mode 100644 index 0000000..767335b --- /dev/null +++ b/holly/MEMORY_MID.md @@ -0,0 +1,3 @@ +# MEMORY_MID.md — [Agent Name TBD] Mid-Term Memory + +*Not yet populated.* diff --git a/holly/MEMORY_SHORT.md b/holly/MEMORY_SHORT.md new file mode 100644 index 0000000..1946b50 --- /dev/null +++ b/holly/MEMORY_SHORT.md @@ -0,0 +1,3 @@ +# MEMORY_SHORT.md — [Agent Name TBD] Recent Session Digest + +*Not yet populated.* diff --git a/holly/PROTOCOLS.md b/holly/PROTOCOLS.md new file mode 100644 index 0000000..eaf4943 --- /dev/null +++ b/holly/PROTOCOLS.md @@ -0,0 +1,7 @@ +# [Agent Name TBD] — Protocols + +*Define Holly's behavioural rules, response style, and any constraints here.* + +--- + +**Placeholder** — fill this in before starting Holly's instance. diff --git a/holly/SOUL.md b/holly/SOUL.md new file mode 100644 index 0000000..b85324b --- /dev/null +++ b/holly/SOUL.md @@ -0,0 +1,8 @@ +# [Agent Name TBD] — Soul & Values + +*Define Holly's personality, values, communication style, and what makes her +distinct from other AI assistants here.* + +--- + +**Placeholder** — fill this in before starting Holly's instance. diff --git a/holly/USER.md b/holly/USER.md new file mode 100644 index 0000000..c1aff89 --- /dev/null +++ b/holly/USER.md @@ -0,0 +1,8 @@ +# User Profile — Holly + +*Document Holly's preferences, interests, and context here so the agent +can personalise responses over time.* + +--- + +**Placeholder** — fill this in before starting Holly's instance.