feat(05-01): AgentTemplate ORM model, migration 007, and system prompt builder

- Add AgentTemplate ORM model to tenant.py (global, not tenant-scoped)
- Create migration 007 with agent_templates table and 7 seed templates
- Create shared/prompts/system_prompt_builder.py with build_system_prompt()
- AI transparency clause always present (non-negotiable per Phase 1 decision)
- Unit tests pass (17 tests, all sections verified)
This commit is contained in:
2026-03-24 20:27:54 -06:00
parent bffc1f2f67
commit d1acb292a1
5 changed files with 626 additions and 0 deletions

View File

@@ -186,6 +186,89 @@ class Agent(Base):
return f"<Agent id={self.id} name={self.name!r} tenant_id={self.tenant_id}>"
class AgentTemplate(Base):
"""
Pre-built AI employee templates available to all tenants.
Templates are NOT tenant-scoped (no tenant_id, no RLS). Any authenticated
portal user can browse templates. Only tenant admins can deploy them.
Deploying a template creates an independent Agent snapshot — subsequent
changes to the template do not affect deployed agents.
"""
__tablename__ = "agent_templates"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
role: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
comment="2-3 sentence card preview description shown in the template gallery",
)
category: Mapped[str] = mapped_column(
String(100),
nullable=False,
default="general",
comment="Template category: support | sales | operations | finance | general",
)
persona: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
comment="Paragraph describing the agent's communication style and personality",
)
system_prompt: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
comment="Full system prompt including AI transparency clause",
)
model_preference: Mapped[str] = mapped_column(
String(50),
nullable=False,
default="quality",
comment="quality | balanced | economy | local",
)
tool_assignments: Mapped[list[Any]] = mapped_column(
JSON,
nullable=False,
default=list,
comment="JSON array of tool name strings",
)
escalation_rules: Mapped[list[Any]] = mapped_column(
JSON,
nullable=False,
default=list,
comment="JSON array of {condition, action} escalation rule objects",
)
is_active: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
comment="Inactive templates are hidden from the gallery",
)
sort_order: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="Display order in the template gallery (ascending)",
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
)
def __repr__(self) -> str:
return f"<AgentTemplate id={self.id} name={self.name!r} category={self.category!r}>"
class ChannelConnection(Base):
"""
Links a messaging platform workspace to a Konstruct tenant.

View File

@@ -0,0 +1 @@
# Konstruct system prompt utilities

View File

@@ -0,0 +1,68 @@
"""
System prompt builder for Konstruct AI employees.
Assembles a coherent system prompt from wizard inputs (name, role, persona,
tools, escalation rules) and always appends the mandatory AI transparency
clause. The transparency clause is non-negotiable and cannot be omitted.
"""
from __future__ import annotations
# Non-negotiable AI transparency clause (Phase 1 architectural decision).
# Agents must always disclose their AI nature when directly asked.
AI_TRANSPARENCY_CLAUSE = (
"When directly asked if you are an AI, always disclose that you are an AI assistant."
)
def build_system_prompt(
name: str,
role: str,
persona: str = "",
tool_assignments: list[str] | None = None,
escalation_rules: list[dict[str, str]] | None = None,
) -> str:
"""
Build a system prompt for an AI employee from wizard inputs.
Args:
name: The agent's display name (e.g. "Mara").
role: The agent's role title (e.g. "Customer Support Rep").
persona: Optional paragraph describing the agent's communication style
and personality traits.
tool_assignments: Optional list of tool names available to the agent.
Omitted from the prompt when empty or None.
escalation_rules: Optional list of escalation rule dicts, each with
"condition" and "action" keys. Omitted when empty/None.
Returns:
A complete system prompt string, always ending with the AI transparency
clause.
"""
sections: list[str] = []
# --- Identity header ---
identity = f"You are {name}, {role}."
if persona:
identity = f"{identity}\n\n{persona}"
sections.append(identity)
# --- Tools section (omitted when empty) ---
effective_tools = tool_assignments if tool_assignments else []
if effective_tools:
tool_lines = "\n".join(f"- {tool}" for tool in effective_tools)
sections.append(f"You have access to the following tools:\n{tool_lines}")
# --- Escalation rules section (omitted when empty) ---
effective_rules = escalation_rules if escalation_rules else []
if effective_rules:
rule_lines = "\n".join(
f"- If {rule.get('condition', '')}: {rule.get('action', '')}"
for rule in effective_rules
)
sections.append(f"Escalation rules:\n{rule_lines}")
# --- AI transparency clause (always present, non-negotiable) ---
sections.append(AI_TRANSPARENCY_CLAUSE)
return "\n\n".join(sections)