§1Context — why we're doing this
Loquent's AI capabilities live in eight disconnected places. Each has its own prompt-building, model resolution, tool list, logging shape, notification path, and permission story. There is no shared identity an end user can name, configure, or watch evolve.
AiUsageFeature variantsmods::agent file refs (voice)mods::text_agent file refsmods::plan refs (unused in prod)What an org owner experiences today
They see a dashboard briefing, a daily report email, three reply suggestions on every inbound message, and call analysis on the call detail page. None of those AI surfaces are aware of each other. The dashboard briefing doesn't know what the daily report told the owner this morning. The reply suggestions don't know what's already in the contact's memory.
What we want them to experience
One mental model: "These are my agents. They have personalities, they follow rules I taught them, they use skills I gave them, and they're learning from how I use them."
- One identity per agent (org-scoped, kind-tagged, persona on file)
- One creation flow: describe what you want, the agent extracts the config, you tweak and save
- One timeline per run, with cost and latency per step
- One rule system: platform rules layer under org rules layer under per-agent rules
- One skill library: platform-curated + org-curated, attachable per agent
- One learning loop: daily reflections, lessons from failures, lessons-into-prompts
Plans are not in production
The mods::plan/ code is sophisticated (typed tool-call log, JSONB state machine, cron executor) but no production org runs plans today. We borrow ideas from that code — but we do not retarget it. The new ai_agent system is built fresh; mods::plan/ is freely deletable.
§2Research summary
2.1 Hermes Agent — the source of inspiration
You mentioned Hermes Agent as the model. The reference is Nous Research's Hermes Agent — a self-improving multi-agent framework where every agent is a profile: an isolated unit with its own configuration, persona (SOUL.md), memory database, tool list, and cron jobs. Profiles connect independently to Telegram, Discord, Slack, Email gateways. Each is a complete CLI entity with its own personality.
What we steal from Hermes
The SOUL.md pattern — codifying persona as a first-class, shareable record. The isolated-profile concept — each agent is a complete identity. Loquent's adaptation: multi-tenant SaaS where org boundaries matter and agents share infrastructure.
2.2 Agent anatomy across modern frameworks
| Dimension | Hermes | CrewAI | LangGraph | Letta | Loquent |
|---|---|---|---|---|---|
| Persona | SOUL.md | role / goal / backstory | system prompt in node | agent identity | ai_agent.persona JSONB |
| Memory | file DB | short + opt-in long | state object | 3-tier core/recall/archival | ai_agent_memory (short/long) |
| Goals | SOUL.md + cron | tasks in a Crew | graph nodes | memory-driven | ai_agent.goals + per-run |
| Rules | SOUL.md | system prompt | node instructions | constitutional (WIP) | 3-tier (platform / org / agent) |
| Tools | fn-call + skill lib | per-agent list | graph nodes | tool calls | allowlist + skills |
| Access | gateway + env | task-scoped | node-level | fn signatures | ai_agent.access_scope |
| Skills | ~/.skills/ | — | — | — | ai_skill first-class table |
No mainstream framework has built-in multi-tenant access control or organization-scoped rules. Loquent has to design this layer itself.
2.3 Self-evolution — two mechanisms, one evergreen doc
Voyager-style auto-extracted skills are dropped (Skills are curated — see §9). Generative-Agents reflection is folded into a single ai_agent_learning evergreen doc; each version is itself a period snapshot. Reflexion lessons stay as failure-triggered records that feed each learning update.
| Method | Trigger | How Loquent uses it |
|---|---|---|
| Generative Agents Park et al. 2023 | Cadence (weekly per-agent, monthly per-org) | Updates the evergreen ai_agent_learning doc; a new ai_agent_learning_version row is appended each cadence tick |
| Reflexion Shinn et al. 2023 | Failure / user feedback | ai_agent_lesson rows: one-line verbal learnings from failed runs — feed into each learning update + injected directly into prompts |
2.4 Observability — OpenTelemetry GenAI conventions
Since 2024, OpenTelemetry has defined semantic conventions for GenAI tracing. We adopt them in ai_agent_log on day one so a future export to Langfuse / Datadog / Grafana is a config change, not a refactor.
2.5 Policy-as-Prompt — layered rules
2.6 RIG — the chosen Rust LLM framework
0xPlaygrounds/rig is the LLM framework for the new runtime. Native Rust agents, pipelines, RAG, and typed tools. The agent runtime wraps RIG's Agent::builder() with Loquent-specific prompt construction, tool registration, and logging.
Existing aisdk callers in non-agent modules stay until they're retargeted onto ai_agent (and migrate to RIG transitively). No parallel aisdk-vs-RIG abstraction layer.
§3Current state in Loquent
Two modules already do most of what we need for the UI. They just don't know about each other.
3.1 Plans + Templates — not in production
mods::plan/ (71 file references) is a sophisticated autonomous-execution framework: typed tool-call log, 8-state JSONB state machine, cron-polled executor, contact assignments, autopilot gating, re-enrollment policy. Hard problems solved. But no production org uses it.
Implication
No retargeting, no feature-flag-gated coexistence, no parity tests on plan execution. The code stays untouched during agent development and is removed in a dedicated cleanup PR (suggested timing: between Phase 5 and Phase 6).
What we borrow as ideas — typed ToolCallVariant<I, O> pattern (per-kind tool enums), the 8-state machine shape, the cron poller pattern, the dual-write log shape.
3.2 Text Agent (mods::text_agent) — 39 file refs
Generates 3 suggested replies per inbound. Already has an NLP-driven setup flow at mods/text_agent/views/setup/ — this pattern is reused for the new agent NLP creation in Phase 4.
3.3 Voice Agent (mods::agent) — 54 file refs
Realtime/streaming voice agent. Owns the type Agent, the AgentResource permission, the phone_number.agent_id FK. Also has a setup wizard at mods/agent/views/setup/ — another reference for Phase 4 NLP creation.
Naming collision — voice agent stays
Voice agent owns the name Agent in code. We use AiAgent internally. UI shows "Agents" (for the new abstraction) vs "Voice Agents" (for the existing). Voice rename deferred to optional Phase 14.
3.4 AI infrastructure (mods::ai)
ai_usage_logtable + 26-variantAiUsageFeatureenum atsrc/mods/ai/types/ai_usage_type.rs:8-37. Stays stable. EachAiAgentKindmaps to an existing variant.spawn_log_ai_usage(AiUsageEntry)atsrc/mods/ai/services/log_ai_usage_service.rs:13- OpenRouter as default provider
3.5 Scheduling, signup, permissions
cron_tabcrate; jobs registered atsrc/app/jobs/app_jobs.rs:17- Signup hook at
src/mods/signup/services/finalize_signup_service.rs:174— where Phase 2 insertsseed_default_agents define_resources!atsrc/bases/auth/types/resources.rs:84-88— where new permission blocks (AiAgent,SystemAiAgent,AiSkill) go
§4Target architecture — the AiAgent concept
Internal type / table / permission is AiAgent. UI label is "Agents". The voice agent stays as is.
4.1 The 6 dimensions
4.2 System (base) agents — DB-resident, admin-managed
System agents are stored as regular ai_agent rows with is_system_agent = true. Loquent admins create / edit them via an admin UI (Phase 1). Sign-up clones them into each new org (Phase 2) with is_system_default = true and system_source_id pointing back.
When an org admin edits any field on a clone → is_system_default flips to false in the same transaction. Orphans the row from future platform-template updates (so a customer's edits aren't overwritten).
Why DB rather than Rust constants?
Platform staff can iterate on system-agent personas, goals, default rules, and tool allowlists without shipping a release. The admin UI is the editing surface — same shape as the org-facing edit form, just gated by a different permission.
4.3 Agent kinds (Rust enum)
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum AiAgentKind {
// Phase 5-7 — initial runnable kinds
DashboardBriefingAgent,
TextReplyAgent,
CustomReportAgent,
// Phase 10 — onboarded existing AI ops
AnalyzerAgent,
TaskExtractionAgent,
AutoTagAgent,
ContactEnrichmentAgent,
ContactMemoryAgent,
AssistantAgent,
// Phase 11 — meta-agent
ReflectionAgent,
}
4.4 The runtime — RIG inside run_ai_agent
pub async fn run_ai_agent(
ctx: &AppContext,
agent_id: Uuid,
trigger: AiAgentTriggerSource,
input_payload: serde_json::Value,
) -> AppResult<AiAgentRun> {
// 1. Load AiAgent + assigned skills + recent lessons
// 2. Resolve layered rules (platform + org + agent)
// 3. Build RIG Agent with:
// system_prompt = persona + goals + rules + skills + lessons + kind_instructions
// tools = resolve(tools_allowlist)
// model = agent.model_override.or(org_default_model)
// 4. Create AiAgentRun (state=Executing) + emit Run.Started log
// 5. Drive RIG event loop, logging every event to ai_agent_log
// 6. On completion: state=Completed + aggregate cost/tokens
// 7. Flush all logs in batches
}
§5Data model
5.1 New tables
| Table | Purpose | Phase |
|---|---|---|
ai_agent | The agent identity (all 6 dimensions + system-agent flags) | 0 |
ai_agent_run | One execution; state machine, parent/child, trigger source | 0 |
ai_agent_log | Unified, OTel-compatible timeline; typed per kind via JSONB | 0 |
ai_agent_memory | Short-term scratchpad + long-term facts (pgvector deferred) | 0 |
ai_skill | Curated skills — title, when-to-use, body. Platform or org scope. | 0 schema, used Phase 8 |
ai_agent_skill_link | M2M between agents and skills | 0 |
ai_agent_schedule | Cron-driven runs; FOR UPDATE SKIP LOCKED dispatch | 7 |
platform_ai_rule | Immutable platform-tier rules; seeded by migration | 9 |
ai_agent_learning | Evergreen AI-curated doc — one per agent + one per org; current_version_id FK | 11 |
ai_agent_learning_version | Full version history of the learning doc; each version is a period snapshot | 11 |
ai_agent_lesson | Reflexion verbal lessons from failures — feed updates + injected to prompts | 11 |
ai_agent_version | Optional version history (auto-title + change description) | 12 (deferred) |
5.2 Key columns on ai_agent
| Column | Type | Notes |
|---|---|---|
organization_id | Uuid? | NULL for system agents (per Q12) |
kind | VARCHAR | AiAgentKind serde tag |
is_system_agent | BOOL | Platform-owned base template |
is_system_default | BOOL | Unedited clone of a system agent |
system_source_id | Uuid? FK ai_agent | Points clones back to source |
persona | JSONB | { tone, voice, signature, traits, avoid, soul_md? } |
goals | JSONB | Vec<AiAgentGoal> |
rules_override | JSONB | Vec<String> — per-agent rules |
tools_allowlist | JSONB | Vec<ToolName> |
access_scope | JSONB | { read[], write[], owner_user_id? } |
budget | JSONB | Daily / monthly cents, per-run tokens, spent today/month |
enable_reflection | BOOL | Opt-in to self-evolution |
enable_autopilot | BOOL | Tool calls execute without approval |
5.3 The ai_agent_log.entry typed payload
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AiAgentLogEntry {
Thought(AiAgentLogThought),
LlmGeneration(AiAgentLogLlmGeneration),
ToolCall(AiAgentLogToolCall), // typed per kind via ToolCallVariant<I,O>
SystemEvent(AiAgentLogSystemEvent),
Reflection(AiAgentLogReflection),
Failover(AiAgentLogFailover),
}
Memory model — flagged for research
The ai_agent_memory table above is the v1 placeholder, modeled on Loquent's existing contact-memory pattern (a single string updated in place by AI). The deeper architecture — tiered memory (core / recall / archival per Letta), semantic recall via embeddings, structured-vs-freeform shape, attribution to specific tools or facts — is deferred as a dedicated research deliverable post-v1. Build the v1 schema; revisit before Phase 10 onboards heavy memory writers like ContactMemoryAgent.
5.4 Existing tables — additive changes
text_agent→ addai_agent_id Uuid? FK ai_agent(Phase 6)contact_note→ addwritten_by_ai_agent_run_id Uuid? FK ai_agent_run(Phase 10)
No ai_agent_id columns on plan or plan_template. Those modules are deprecated.
§6Migration phases — UI first, kinds after
Front-loading UI is the key shift from v1. By the time the first kind goes live in Phase 5, the org-facing surfaces are already polished.
-
PHASE 0 Foundations — first PRM · ~1 week
Core tables (
ai_agent,ai_agent_run,ai_agent_log,ai_skill,ai_agent_skill_link,ai_agent_memory) + Rust types + RIG-powered generic executor + admin-only debug endpoint. AddAiAgent,SystemAiAgent,AiSkillpermission resources. No user-visible change. Build foundation; ship behind admin gate. -
PHASE 1 Platform admin areaL · ~2 weeks
Loquent-admin-only UI to list, create, edit, archive system agents. Gated by
SystemAiAgentpermission. All 6 dimensions editable. Reusable components (agent_persona_editor,agent_goals_editor, etc.) drive both this admin UI and the org UI in Phase 3. -
PHASE 2 Sign-up seedingS · 3–4 days
New orgs receive a deep-cloned copy of each active system agent on signup. Clones get
is_system_default = true+system_source_idset. Non-blocking (matches existing notification-preference seeder). -
PHASE 3 Org UI — list + detail + editXL · ~3 weeks
Org-facing agent management. Card grid (kind icon, status, primary metric). Detail view with sections (Identity, Persona, Goals, Rules, Tools, Access, Schedule, Recent Runs, Metrics). Edit form with progressive disclosure. Edit handler flips
is_system_defaultto false on any field change. "Customized" / "Using Platform Default" badge. -
PHASE 4 Org UI — NLP-driven creationL · ~2 weeks
"Describe the agent you want" chat-driven creation. Reuses the existing setup-wizard pattern from
mods/agent/views/setup/(voice) andmods/text_agent/views/setup/(text). RIG-based extraction agent populates: name, kind, persona, goals, suggested tools_allowlist, schedule. Confirmation step before save. Blank-form fallback always available. -
PHASE 5 Dashboard Briefing Agent — first running kindM · ~1 week
Retargets
generate_dashboard_briefing_servicetorun_ai_agent(DashboardBriefingAgent, ...). Lowest blast radius (failures are cosmetic). Per-org feature flag; 7-day A/B parity test against legacy. -
PHASE 6 Text Reply AgentL · ~2 weeks
Refactor
mods/text_agentto back persona/rules onai_agent. Structured output (RIGschema::<T>()) for 3-suggestion JSON. Auto-fallback to legacy on parse failure. Per-org flag, 10% → 100% rollout. -
PHASE 7 Custom Report Agent + schedulingL · ~2 weeks
Replace
send_daily_report_jobwithrun_due_ai_agents_jobdriven byai_agent_schedule.FOR UPDATE SKIP LOCKEDdispatch. Schedule editor UI: cron-spec builder + recipient picker + section toggles. -
PHASE 8 Skills — first-class curatedL · ~2 weeks
Platform + org skill curation UIs. Skill picker on agent edit. Prompt builder injects enabled+linked skills into the system prompt. Migration seeds initial platform skills ("Email Tone Guide", "SMS Length & Compliance", "Daily Briefing Structure", "Contact Note Etiquette"). Default body length cap 4000 chars.
-
PHASE 9 Layered rulesM · ~1 week
3-tier rule injection.
platform_ai_ruleseeded via migration; org rules editable; agent rules editable. Each run snapshots its effective rules inai_agent_log. UI shows platform rules read-only. -
PHASE 10 Onboard remaining AI operationsXL · parallelizable
One PR per kind:
AnalyzerAgent,TaskExtractionAgent,AutoTagAgent,ContactEnrichmentAgent,ContactMemoryAgent,AssistantAgent. Each replaces inline aisdk calls withrun_ai_agent. Orchestration stays where it is. -
PHASE 11 Self-evolution — reflections + lessonsXL · ~3 weeks
Evergreen
ai_agent_learningdoc (agent + org scope), AI-refined weekly per-agent + monthly per-org. Full versioned history for evolution visualization. Reflexion lessons from failures feed each update.LearningAgentkind. Views:/agents/{id}/learning+/org/learning. Opt-in per org; manual trigger available; admin can roll back versions. -
PHASE 12 Versioning (nice-to-have)M · ~1 week
Optional version history. On save: snapshot full agent state, auto-generate version title, accept optional change description. "History" tab on detail view. "Restore version". Retention policy (last 20 per agent by default).
-
PHASE 13 Budgets + observability UIL · ~2 weeks
Enforce per-agent + per-org budgets before each LLM call. Ship the unified timeline view + cost dashboards.
/agents/{id}/runs/{run_id}timeline,/agents/{id}/metrics. -
PHASE 14 Voice agent rename — optional, deferredL · mechanical
Rename
mods/agent/→mods/voice_agent/. Only afterai_agentruntime is stable for ≥6 months. Skip if no friction emerges. -
PHASE X Plans / Templates cleanupS · 1 PR
Delete
mods/plan/. Drop the related tables. RemovePlanandPlanTemplatefromdefine_resources!. Suggested timing: between Phase 5 and Phase 6, when confidence in the new runtime is high.
§7Observability — every operation, indexed
Every operation an agent performs is a row in ai_agent_log. Indexed for fast timeline rendering, fast cost dashboards, and as the input corpus for the reflection pipeline.
kind | When | What's in entry |
|---|---|---|
system_event | Run started, state transition, schedule advanced, budget enforced, rules applied | { event, prev_state?, new_state?, reason? } |
thought | LLM produced reasoning text | { text, confidence? } |
llm_generation | Every RIG completion (prompt → response) | { provider, model, finish_reason, prompt_summary, completion_summary } + ai_usage_log_id FK |
tool_call | Every typed tool invocation | { tool_name, input, output, status, duration_ms } — typed per kind via ToolCallVariant<I,O> |
reflection | A reflection cycle ran | { scope, period_start, period_end, source_log_ids[] } |
failover | New path failed, legacy path took over | { from_path, to_path, reason } |
OpenTelemetry attribute mapping
| Loquent field | OTel attribute |
|---|---|
ai_agent_run.trace_id | trace.id |
ai_agent_log.span_id | gen_ai.* span id |
ai_agent.kind | agent.kind (custom) |
ai_usage_log.model | gen_ai.request.model |
ai_usage_log.input_tokens | gen_ai.usage.input_tokens |
ai_usage_log.cached_tokens | gen_ai.usage.cache_read_tokens |
| (computed) | cost_cents (custom) |
ai_agent_log.duration_ms | span duration |
Indexes for fast analysis
(ai_agent_run_id, created_at)— timeline render(ai_agent_id, created_at DESC)— "show me this agent's recent activity"(organization_id, created_at DESC)partialWHERE kind IN ('llm_generation','tool_call')— cost dashboards- GIN on
entryJSONB — attribute search (e.g. "find all tool calls where input.to_number = '+1...'")
§8Self-evolution — Learning + Lessons
A single evergreen Learning doc per agent + per org, AI-refined on cadence with full version history. Reflexion lessons from failures feed each update. No auto-extracted skills — those are curated.
Two scopes
- Agent learning — what this specific agent has learned about its own runs (e.g. "DashboardBriefingAgent has learned to lead with new leads, then handled calls, because the owner clicks those sections first").
- Org learning — cross-cutting patterns across all the org's agents (e.g. "This org's customers respond more to morning messages; weekend messages get lower response rates").
The learning document shape
Markdown. The LearningAgent rewrites it in-place each cadence tick — adding new patterns, integrating recent runs, retiring outdated observations. Each rewrite produces a new ai_agent_learning_version row with the full new content and a what_changed summary. The version timeline IS the evolution visualization.
What feeds each update
- The window of
ai_agent_logentries since the last update - Recent
ai_agent_lessonrows (Reflexion failure verbals) - The current learning version (so the AI knows what's already captured)
- For org-level: all per-agent learning versions from the past month
Closing the loop
build_ai_agent_prompt_service injects the agent's current learning version + the org's current learning + recent ai_agent_lesson rows into every run's system prompt. Skills (curated) layer alongside.
Cost discipline
Opt-in per org via ai_agent.enable_reflection. 14-day free trial. Cheapest learning model (e.g. deepseek-v3.2). Per-agent monthly token budget — if exceeded, the next update is skipped and a system_event log is written. Manual trigger doesn't bypass the budget.
Recoverable drift
If a learning update goes off the rails, admin can roll back to a prior version, or edit the markdown directly. The what_changed summary on each version is the audit trail.
§9Skills — first-class curated
Skills are reusable knowledge capsules — like Claude Code skills or Hermes skills. Title, short description (agent-focused "when to use this"), and body (the actual instructions). Curated by humans, attached to agents, also usable by the Loquent assistant.
| Field | Purpose |
|---|---|
title | Short label, e.g. "Draft Daily Summary Email" |
short_description | Agent-focused: "Use this when composing a daily summary email for the org owner. Includes formatting guidelines, recipient tone, sign-off." |
body_markdown | The instructions / template / guidance the agent reads. Cap default 4000 chars. |
organization_id | NULL = platform-curated; non-null = org-curated |
tags | Filtering / search |
is_platform_curated | True if part of Loquent's curated set |
Scopes
- Platform skills (
organization_id = NULL) — admin-curated, available to all orgs - Org skills — org-admin curated, scoped to that org
Assignment
Agents reference skills via the ai_agent_skill_link M2M table. At prompt-build time, all enabled+linked skills are loaded into the system prompt (Q11: load-all approach for v1; LLM-driven activation is a future evolution).
Initial platform-curated skill seed
- Email Tone Guide — formatting / sign-off conventions
- SMS Length & Compliance — 160-char awareness, opt-out language
- Daily Briefing Structure — what sections, what order
- Contact Note Etiquette — what's appropriate to record
More added by Loquent staff over time via the admin UI.
Skills vs Rules — the distinction
Rules are constraints ("never quote a price you can't honor"). Layered — platform wins.
Skills are capabilities ("here's how to draft a daily summary"). Attached per agent; both platform-curated and org-curated coexist; no override semantics.
Skills + the Loquent assistant
The existing mods/assistant/ consumes skills the same way agents do (Phase 8 lights this up for mods/ai_agent; an explicit assistant_skill_link table can be added later if the assistant adopts the same curated-skill pattern).
§10The three initial running kinds
These three become real LLM-calling agents in Phases 5–7. Everything before that is foundation + UI.
10.1 Dashboard Briefing Agent Phase 5
- Kind:
DashboardBriefingAgent - Trigger: Manual (dashboard load) + scheduled (daily 5 AM org-tz)
- Persona default: "Concise, data-driven analyst. Plain English. No jargon."
- Tools:
get_workspace_summary,get_workspace_needs_attention,get_engagement_stats - Output: Markdown briefing on the dashboard
- Wraps:
generate_dashboard_briefing_service.rs
10.2 Text Reply Agent Phase 6
- Kind:
TextReplyAgent - Trigger: Webhook (inbound SMS)
- Persona default: From existing
text_agentrow (migrated) - Tools:
query_knowledge - Output: Structured 3-suggestion JSON (high / med / low confidence)
- Wraps:
generate_text_agent_suggestions_service.rs - Fallback: Auto-fallback to legacy on parse failure
10.3 Custom Report Agent Phase 7
- Kind:
CustomReportAgent - Trigger: Scheduled (
ai_agent_schedulerow) - Persona default: "Helpful daily-digest assistant."
- Config (in
ai_agent_schedule.config_payload):recipients,sections,delivery_channels, optional 500-char custom prompt - Tools:
get_recent_leads,get_handled_calls,get_messages_window,get_priority_tasks - Output: Notifications via existing
notify_service - Wraps:
generate_daily_report_service.rs
§11Org onboarding — clone system agents
Sign-up clones every active system agent into the new org. Org admins inherit a working configuration; their first edit "adopts" the clone.
// In finalize_signup_service.rs, after line 174:
seed_default_agents(&txn, organization_id, owner_user_id).await
.or_log_and_continue();
The seeder reads WHERE is_system_agent = true AND is_active = true and, for each row, performs a deep clone into the new org:
is_system_agent = falseis_system_default = truesystem_source_id = <source_row_id>organization_id = <new_org_id>- All other fields copied verbatim
- Schedule rows + skill-link rows cloned too
The "Customized" flip
When the org admin saves any edit on a clone, the save handler flips is_system_default = false in the same transaction. The UI shows a badge: "Customized" (was system default; now org-owned) versus "Using Platform Default" (untouched clone). Platform-template updates do not propagate to existing clones — orgs own their copies.
§12Layered rules — platform > org > agent
Platform rules are immutable from any UI. Org rules are org-admin editable. Agent rules are per-agent.
Platform rules (immutable; seeded by migration)
- "Never make legal, medical, or financial advice claims."
- "Never share PII outside the org boundary."
- "Always announce when a message is AI-generated if asked."
- "Never autopilot a destructive action without explicit org-owner approval."
- "Always log every tool call with full input/output."
Editing platform rules requires a code change + migration — auditable, reviewable, deployable. Orgs cannot mutate this layer.
Org rules (editable)
- "Sign messages as 'Alex from Acme Co.'"
- "Use first-name only when addressing contacts."
- "Never discuss competitor X."
Agent rules (editable per agent)
- "This text agent only handles inbound questions about pricing."
- "This report agent emails the founder, not the team."
Audit
Each ai_agent_run records its effective rules in ai_agent_log (kind=system_event, event=RulesApplied). If an org rule attempts to override a platform rule, the snapshot shows both and the platform rule wins.
§13Verification plan
How we know each phase is safe to ship.
| Phase | Verification |
|---|---|
| 0 | cargo check + cargo clippy green; migrations apply + roll back; admin endpoint hit; AiAgentRunState transitions unit-tested; RIG builds for server target. |
| 1 | Admin creates + edits + archives a system agent through the UI; all 6 dimensions roundtrip; /test-ui validates. |
| 2 | New signup smoke test in dev creates an org → asserts N clones (N = active system agents). |
| 3 | Org admin: list view, detail view, edit field → "Customized" badge appears. Mobile responsive. /test-ui required. |
| 4 | NLP input "I need a daily 8am email of new leads" → extracts CustomReportAgent + schedule + persona. Editable before save. |
| 5 | 7-day side-by-side parity test: old + new dashboard briefing; cost parity within ±10%; no double-billing. |
| 6 | Nightly synthetic: 100 inbounds → 3 suggestions each via new path, 0 fallbacks. 10% → 100% rollout. |
| 7 | 7-day shadow run of dispatcher against is_enabled=false schedules; verify timing. Then flip a test org. |
| 8 | Skill attached to agent: prompt-build injects body; effective prompt visible in log; ai_usage shows expected token increase. |
| 9 | Run with org rule attempting platform override: snapshot shows both; platform wins. |
| 10 | Per-kind parity test against legacy. |
| 11 | 4-week-old agent: ≥1 learning version (weekly) at /agents/{id}/learning; org has ≥1 monthly version at /org/learning; timeline browsable. Any failed run within the window produces an ai_agent_lesson. Admin can roll back to a prior version. |
| 12 | Edit + save → new version row with auto-title; restore old version → fields match. |
| 13 | Budget exceeded → next run rejected with user-visible message + system_event log. |
End-to-end (after Phase 7)
- Sign up a new org
- Provision a phone number
- Observe
DashboardBriefingAgent,CustomReportAgent,TextReplyAgentclones created - Open dashboard → briefing renders via new path
- Send SMS → 3 suggestions generated
- Wait for daily report cron → email delivered
- Inspect
/agents/{id}/runs/{run_id}timeline for each - Confirm
ai_usage_logrows have matchingai_agent_logrows
§14Open questions for you
Q1–Q10 resolved on 2026-05-26 at recommended defaults. Q11–Q15 are new.
Resolved 2026-05-26
AiAgent internally + UI "Agents". Voice rename deferred to optional Phase 14.mods/plan instead of retargeting.ai_agent_log.New — please confirm or redirect
- (a) Load all enabled-and-attached skills into the system prompt. Predictable cost, simpler implementation. Right starting point for v1.
- (b) Load skill descriptions only; LLM picks via tool call which to load body for. More like Claude Code's runtime model. Higher complexity, deferred.
organization_id)?- (a)
organization_id = NULLfor system agents. Clean separation; FK is nullable. Query:WHERE is_system_agent AND organization_id IS NULL. - (b) A designated platform-org row (e.g.
loquent_internal_org). Simpler FK constraints but requires creating + maintaining that org.
mods::plan/?- (a) Cleanup PR immediately after Phase 0 lands. Bigger first batch.
- (b) Cleanup PR after Phase 5 (Dashboard Briefing Agent) is live. Confidence in the new runtime; no rush.
- (c) Defer indefinitely. Bit-rot risk; dead-code cost.
- (a) Snapshot-on-save into
ai_agent_versionwith configurable retention (default 20). Simple, recoverable, bounded. - (b) Event-sourced changes (one row per field edit). Lower storage, harder restore.
- (a) 2000 chars — tight
- (b) 4000 chars. Useful capsule, still constrained.
- (c) No cap; rely on a prompt-size warning in the picker.
§15References
- Hermes Agent — Nous Research
- Generative Agents — Park et al. (UIST 2023)
- Reflexion — Shinn et al. (NeurIPS 2023)
- Voyager — Wang et al. (2023)
- Anthropic: Building Effective Agents (Dec 2024)
- CrewAI Documentation — Agents & Memory
- Letta (MemGPT) — Agent Memory
- OpenTelemetry GenAI Semantic Conventions
- Langfuse — Open-Source LLM Observability
- Policy-as-Prompt — arXiv:2509.23994
- RIG — Rust LLM Framework
- Claude Code Skills