Home Gallery AISPA Paper GitHub Follow

lotti system prompt

Category: Coding agents. Audited against the AISPA standard.

3 Prompts on record
1 Flagged instructions
AI audit Audit source
D1 · Identity Transparency D2 · Truthfulness & Information Integrity D3 · Privacy & Data Protection D4 · Tool/Action Safety D5 · User Agency & Manipulation Prevention D6 · Unsafe Request Handling D7 · Harm Prevention & User Safety D8 · Fairness, Inclusion & Neutrality

lotti - docs implementation plans 2026 02 22 agent prot...

39670 characters · 1 flagged

# Agent Templates: Learning Task Agents with Feedback Evolution Date: 2026-02-22 Status: Draft for review Depends on: Foundation layer (PRs #2683–#2689, all merged), Agentic Product Direction (§9.1 Persona/Soul Contract) ## 1. Objective Introduce **Agent Templates** — versioned definitions that shape an agent's personality and behavioral directives. Templates are instantiated into agent instances (currently Task Agents) and evolve over time through user feedback in a structured "Weekly 1-on-1" ceremony. ### Key outcomes 1. Users can create and manage multiple agent templates with distinct personalities (e.g., "Laura" the encouraging coach, "Tom" the demanding drill sergeant). 2. Every template edit produces a new **immutable version**; no in-place mutation. 3. Every LLM invocation records which template version was used, enabling analytics and rollback. 4. A "Weekly 1-on-1" inspection mode lets the user review agent performance and evolve the template's directives via voice or text feedback. 5. The system prompt is composed dynamically: rigid scaffold (tools, context rules) + injected user-editable directives. --- ## 2. Architecture Overview ### 2.1 Entity relationship model ```mermaid flowchart TD subgraph agent.sqlite Tmpl["AgentTemplateEntity\n(displayName, kind,\nmodelId, categoryIds)"] V1["TemplateVersion v1\n(directives)\nstatus: archived"] V2["TemplateVersion v2\n(directives)\nstatus: archived"] V3["TemplateVersion v3\n(directives)\nstatus: active"] Head["TemplateHead\n(points to active version)"] Agent1["AgentIdentityEntity\n(Task Agent instance)"] Agent2["AgentIdentityEntity\n(Task Agent instance)"] WakeLog["wake_run_log\n+ template_id\n+ template_version_id"] Tmpl -->|"1:N versions"| V1 Tmpl -->|"1:N versions"| V2 Tmpl -->|"1:N versions"| V3 Head -->|"versionId"| V3 Tmpl -->|"head pointer"| Head Agent1 -->|"templateAssignment\n(AgentLink)"| Tmpl Agent2 -->|"templateAssignment\n(AgentLink)"| Tmpl WakeLog -->|"template_id"| Tmpl WakeLog -->|"template_version_id"| V3 end subgraph db.sqlite Task1["Task (journal domain)"] Task2["Task (journal domain)"] end Agent1 -->|"agentTask\n(AgentLink)"| Task1 Agent2 -->|"agentTask\n(AgentLink)"| Task2 ``` ### 2.2 Template-to-instance relationship A single template can be assigned to many agent instances. Each instance works on its own task but shares the same personality and mission: ```mermaid flowchart LR Tmpl["Template: Laura\n(kind: taskAgent)"] A1["Agent Instance\n(Task: Build auth)"] A2["Agent Instance\n(Task: Fix bug #42)"] A3["Agent Instance\n(Task: Write docs)"] Tmpl --- A1 Tmpl --- A2 Tmpl --- A3 ``` ### 2.3 Version lifecycle ```mermaid flowchart LR V1["v1\n(archived)"] --> V2["v2\n(archived)"] --> V3["v3\n(active)"] Head["TemplateHead"] -->|"points to"| V3 Rollback["Rollback\naction"] -.->|"moves head to"| V2 style V3 fill:#2d6,stroke:#1a4,color:#fff style V1 fill:#888,stroke:#666,color:#fff style V2 fill:#888,stroke:#666,color:#fff ``` ### Design principles - **Append-only versioning**: Template versions are immutable once created. The "current" version is tracked by a head pointer (like `AgentReportHead`). - **Separation of concerns**: The template defines *who the agent is*; the agent instance defines *what it's working on*. A single template can be assigned to many agent instances. Templates are a separate domain concept but share the `agent_entities` table and sync infrastructure — distinguished by their `type` discriminator. - **Canonical assignment via link**: The `AgentLink.templateAssignment` link is the single source of truth for which template an agent uses. There is no redundant `templateId` field in `AgentConfig`. - **Prompt composition**: The system prompt is assembled at wake time from: `[rigid scaffold] + [directives from active template version]`. The scaffold contains tool definitions, context rules, and output format instructions. The directives are user-editable. - **Clean-slate database**: The agent feature is unreleased; test users start with a fresh `agent.sqlite`. No migration or backward-compatibility shims are needed — templates are a first-class requirement from day one. --- ## 3. Data Model Changes ### 3.1 New `AgentDomainEntity` variants Add three new variants to the `AgentDomainEntity` freezed sealed union: ```dart /// Agent Template identity — the template itself. /// Stored in `agent_entities` to reuse the existing sync infrastructure. /// The `agentId` field equals `id` (self-referential) since templates are /// top-level entities, not children of an agent. const factory AgentDomainEntity.agentTemplate({ required String id, required String agentId, // self-referential (template's own ID) required String displayName, required AgentTemplateKind kind, // taskAgent (extensible) required String modelId, // default LLM model for this template required Set<String> categoryIds, // categories this template is available for required DateTime createdAt, required DateTime updatedAt, required VectorClock? vectorClock, String? coverArtId, // future V2: JournalImage ID for avatar DateTime? deletedAt, }) = AgentTemplateEntity; /// Immutable version of a template's directives. const factory AgentDomainEntity.agentTemplateVersion({ required String id, required String agentId, // parent template ID required int version, // monotonically increasing per template required String directives, // unified personality, tone, goals, boundaries required AgentTemplateVersionStatus status, // active, archived required String authoredBy, // 'user', 'agent', 'system' required DateTime createdAt, required VectorClock? vectorClock, String? diffFromVersionId, // previous version this was derived from String? sourceSessionId, // 1-on-1 session that produced this version String? approvedBy, // 'user' when explicitly approved DateTime? approvedAt, DateTime? deletedAt, }) = AgentTemplateVersionEntity; /// Head pointer to the active version of a template. const factory AgentDomainEntity.agentTemplateHead({ required String id, required String agentId, // template ID required String versionId, // points to active AgentTemplateVersionEntity required DateTime updatedAt, required VectorClock? vectorClock, DateTime? deletedAt, }) = AgentTemplateHeadEntity; ``` ### 3.2 New enums Add to `agent_enums.dart`: ```dart /// Kind of agent template. New kinds are added as they are implemented. enum AgentTemplateKind { /// Task management agent. taskAgent, } /// Status of a template version in its lifecycle. enum AgentTemplateVersionStatus { /// Active — currently used for new invocations. active, /// Archived — superseded by a newer version, preserved for history. archived, } ``` ### 3.3 New `AgentLink` variant Add a new link variant for template assignment: ```dart /// Links a template to an agent instance it is assigned to. const factory AgentLink.templateAssignment({ required String id, required String fromId, // template ID required String toId, // agent instance ID required DateTime createdAt, required DateTime updatedAt, required VectorClock? vectorClock, DateTime? deletedAt, }) = TemplateAssignmentLink; ``` ### 3.4 Wake run log extension Add `template_id` and `template_version_id` columns directly to the `wake_run_log` table definition (no migration needed — fresh database): ```sql -- In agent_database.drift, modify the existing CREATE TABLE CREATE TABLE wake_run_log ( ... template_id TEXT, -- NEW: which template drove this wake template_version_id TEXT, -- NEW: which version of that template ... ); ``` These columns record which template and version were active at the time of each wake, enabling: - Analytics on version effectiveness (success rate, report quality per version) - Filtering all wakes by template without joining through version entities - Audit trail for debugging behavioral changes - Rollback analysis (compare outcomes across versions) ### 3.5 `AgentConfig` — no changes The `AgentConfig` class is **not** modified. Template assignment is tracked exclusively via the `AgentLink.templateAssignment` link — there is no redundant `templateId` field in the config. This avoids dual-storage inconsistency and keeps a single source of truth. ### 3.6 Type mapping updates In `agent_db_conversions.dart`, add type discriminators: - `'agentTemplate'` → `AgentTemplateEntity` - `'agentTemplateVersion'` → `AgentTemplateVersionEntity` - `'agentTemplateHead'` → `AgentTemplateHeadEntity` - `'templateAssignment'` → `TemplateAssignmentLink` ### 3.7 No drift schema change for entities/links The `agent_entities` and `agent_links` tables use `type` discriminators and `serialized` JSON. New entity/link variants are stored in existing tables with new `type` values. The `wake_run_log` column addition is made directly in the `.drift` file (clean-slate database, no migration needed). --- ## 4. Business Logic & Service Layer ### 4.1 `AgentTemplateService` New service at `lib/features/agents/service/agent_template_service.dart`: ```dart class AgentTemplateService { // ── Template CRUD ── /// Create a new template with an initial version. Future<AgentTemplateEntity> createTemplate({ required String displayName, required AgentTemplateKind kind, required String modelId, required String directives, required Set<String> categoryIds, // categories this template is available for }); /// List all templates, optionally filtered by kind. Future<List<AgentTemplateEntity>> listTemplates({ AgentTemplateKind? kind, }); /// List templates available for a given category. /// Returns templates whose `categoryIds` contain [categoryId]. Future<List<AgentTemplateEntity>> listTemplatesForCategory( String categoryId, { AgentTemplateKind? kind, }); /// Get a template by ID. Future<AgentTemplateEntity?> getTemplate(String templateId); /// Soft-delete a template (only if no active agent instances reference it). /// Active instances must be destroyed first before the template can be deleted. Future<void> deleteTemplate(String templateId); // ── Version management ── /// Get the active version for a template. Future<AgentTemplateVersionEntity?> getActiveVersion(String templateId); /// Get all versions for a template (newest first). Future<List<AgentTemplateVersionEntity>> getVersionHistory( String templateId, ); /// Create a new version (automatically archives the previous active one). /// Returns the new version entity. Future<AgentTemplateVersionEntity> createVersion({ required String templateId, required String directives, required String authoredBy, // 'user' or 'agent' String? sourceSessionId, }); /// Rollback to a previous version by moving the head pointer. Future<void> rollbackToVersion({ required String templateId, required String versionId, }); // ── Assignment queries ── /// Get the template assigned to an agent instance (via templateAssignment link). Future<AgentTemplateEntity?> getTemplateForAgent(String agentId); /// Get all agent instances using a given template. Future<List<AgentIdentityEntity>> getAgentsForTemplate(String templateId); } ``` Note: Template assignment is performed during agent creation in `TaskAgentService.createTaskAgent`, which creates the `templateAssignment` link in the same transaction. There is no separate `assignToAgent` method — an agent's template is set at creation time and cannot be reassigned. To switch templates, the user destroys the agent and creates a new one with the desired template (see §4.6). ### 4.2 Prompt composition changes The system prompt is assembled at wake time by layering user-editable documents onto a rigid scaffold: ```mermaid flowchart TD subgraph "System Prompt Assembly (at wake time)" direction TB Scaffold["Rigid Scaffold (non-editable)\n- Tool definitions & constraints\n- Report format contract\n- Observation rules\n- Category scope enforcement"] Directives["Directives (user-editable)\n- Personality & tone\n- Communication style\n- Goals & boundaries\n- Decision-making style"] Prompt["Assembled System Prompt"] Scaffold --> Prompt Directives --> Prompt end Tmpl["Active TemplateVersion"] -->|"provides"| Directives Code["Hardcoded in TaskAgentWorkflow"] -->|"provides"| Scaffold ``` Modify `TaskAgentWorkflow` to compose the system prompt dynamically: ```dart Future<String> _buildSystemPrompt(AgentIdentityEntity agent) async { // 1. Load assigned template and active version via the templateAssignment link. // Every agent must have a template — this is enforced at creation time. final template = await templateService.getTemplateForAgent(agent.id); final version = await templateService.getActiveVersion(template!.id); // 2. Build scaffold (rigid, non-editable). final scaffold = _buildScaffold(); // tool definitions, output format, rules // 3. Inject directives from the active template version. return ''' $scaffold ## Your Personality & Directives ${version!.directives} '''; } ``` The scaffold contains everything that is NOT user-editable: - Tool usage guidelines and constraints - Report format requirements (`update_report` contract) - Observation recording rules - Category scope enforcement rules - Context injection format The directives field is user-editable and covers everything about the agent's character and approach in a single unified document: personality, tone, communication style, goals, behavioral boundaries, and decision-making preferences. The current hardcoded `taskAgentSystemPrompt` constant is decomposed: its tool/report/observation rules become the scaffold, and the personality-neutral tone becomes the seed content for the default templates (Laura, Tom). > **Future direction — reusable soul entities**: The directives field is a single document for now. Eventually, the personality/identity portion ("Laura") could be extracted into a standalone, reusable **Soul** entity that can be plugged into multiple templates. This would let Laura power both a task agent and a coach template while maintaining a consistent personality. For now, the unified field is simpler and avoids premature abstraction. ### 4.3 Wake cycle with template integration The existing wake cycle is extended to resolve the template and inject its directives into the system prompt: ```mermaid flowchart TD Wake["WakeOrchestrator\ntriggers wake"] --> LoadAgent["Load AgentIdentityEntity"] LoadAgent --> LoadState["Load AgentStateEntity\n(activeTaskId)"] LoadState --> ResolveTmpl["Resolve template\nvia templateAssignment link"] ResolveTmpl --> ResolveVersion["Load active version\nvia TemplateHead"] ResolveVersion --> RecordVersion["Record template_id +\ntemplate_version_id\nin wake_run_log"] RecordVersion --> ResolveModel["Resolve model ID\n(instance > template > fallback)"] ResolveModel --> BuildPrompt["Build system prompt\n(scaffold + directives)"] BuildPrompt --> BuildContext["Build user message\n(task context, report,\nobservations)"] BuildContext --> LLM["Send to LLM\n(with tool definitions)"] LLM --> ToolCalls["Execute tool calls\nvia AgentToolExecutor"] ToolCalls --> PersistResults["Persist report,\nobservations, state"] style ResolveTmpl fill:#28a,stroke:#167,color:#fff style ResolveVersion fill:#28a,stroke:#167,color:#fff style RecordVersion fill:#28a,stroke:#167,color:#fff style BuildPrompt fill:#28a,stroke:#167,color:#fff ``` In `TaskAgentWorkflow.execute()`, after resolving the template version, record it in the wake run log: ```dart // After creating the wake run log entry, record the template provenance. await agentRepository.updateWakeRunTemplate( runKey: runKey, templateId: template.id, templateVersionId: activeVersion.id, ); ``` ### 4.4 Model ID resolution Currently the model ID is hardcoded as `_modelId = 'models/gemini-3.1-pro-preview'`. With templates, the resolution order becomes: ```mermaid flowchart TD Start["Resolve model ID"] --> Check1{"AgentConfig.modelId\n(instance override)\nset?"} Check1 -->|"Yes"| Use1["Use instance modelId"] Check1 -->|"No"| Use2["Use template modelId\n(always present,\ntemplate is required)"] style Use1 fill:#2d6,stroke:#1a4,color:#fff style Use2 fill:#28a,stroke:#167,color:#fff ``` 1. `AgentConfig.modelId` on the agent instance (highest priority, per-instance override) 2. `AgentTemplateEntity.modelId` on the assigned template (always available — template assignment is mandatory) ### 4.5 Default template seeding On first launch (or when the feature flag is enabled), seed two built-in templates: 1. **"Laura"** — Encouraging, supportive, celebrates progress, uses positive reinforcement. - Directives: warm tone, uses encouragement, highlights achievements before gaps, helps the user stay on track with gentle nudges, celebrates milestones 2. **"Tom"** — Direct, no-nonsense, focuses on accountability, pushes for results. - Directives: crisp tone, uses direct language, prioritizes blockers and deadlines, holds the user accountable, flags missed estimates, pushes for completion These are editable — they serve as starting points the user can customize. Seeding runs as part of Phase 1 (see §9) because templates are required for agent creation. The seed logic is idempotent — it checks whether templates already exist before creating. ### 4.6 Switching templates (destroy and recreate) An agent's template assignment is **immutable after creation**. The agent's entire history (messages, observations, reports) was produced by the assigned template's personality, and mixing personalities within a single agent's history would produce incoherent context. To switch an agent to a different template: 1. **Destroy** the existing agent (preserves history for audit via `AgentLifecycle.destroyed`) 2. **Create** a new agent for the same task with the desired template This flow is surfaced in the agent detail page (see §5.5). --- ## 5. UI Implementation ### UI navigation flow ```mermaid flowchart TD Settings["Settings Page"] -->|"Agents card"| TmplList["Agent Templates\nList Page"] TmplList -->|"Tap template"| TmplDetail["Template Detail\n/ Edit Page"] TmplList -->|"+ button"| CreateTmpl["Create Template\n(inline or page)"] TmplDetail -->|"Version History"| VersionList["Version History\n(expandable section)"] TmplDetail -->|"Active Instances"| AgentDetail["Agent Detail Page\n(existing)"] TmplDetail -->|"Weekly 1-on-1"| OneOnOne["1-on-1 Inspection\nPage"] OneOnOne -->|"Evolve Template"| Preview["Evolution Preview\n(diff view)"] Preview -->|"Approve"| TmplDetail Preview -->|"Reject"| OneOnOne TaskHeader["Task Header\n(task_header_meta_card)"] -->|"Create Agent"| TmplSelect{"Template\nselection"} TmplSelect -->|"auto / pick"| AgentDetail style OneOnOne fill:#2d6,stroke:#1a4,color:#fff style Preview fill:#28a,stroke:#167,color:#fff ``` ### 5.1 Settings / Agents list page **Location**: `lib/features/agents/ui/agent_template_list_page.dart` **Navigation**: Settings → Agents (new card in `settings_page.dart`, gated by `enableAgentsFlag`) **Layout**: - App bar: "Agent Templates" with add (+) action - Body: List of template cards, each showing: - Display name (e.g., "Laura") - Kind badge (e.g., "Task Agent") - Model ID (e.g., "Gemini 3.1 Pro") - Active version number (e.g., "v3") - Number of active agent instances using this template - Placeholder avatar circle (future V2: cover art) - Tap → navigate to template detail/edit page - FAB or app bar action → create new template ### 5.2 Template detail/edit page **Location**: `lib/features/agents/ui/agent_template_detail_page.dart` **Layout** (scrollable): 1. **Header section**: - Display name (editable text field) - Model selector (dropdown, populated from `AiConfigRepository`) - Category selector (multi-select, populated from categories — defines which task categories this template is available for) - Avatar placeholder (future V2) 2. **Directives section**: - Multi-line text field with markdown support hint - "Define the agent's personality, tone, goals, and style..." - Example placeholder text showing how to combine personality + mission in one document 3. **Save button**: - Creates a new version (not in-place edit) - Shows confirmation: "This will create version N+1. The agent will use the new version on its next wake." 4. **Version History section** (expandable): - List of all versions with version number, date, authored-by, status badge - Tap to view full content (side-by-side comparison with current active version) - "Rollback to this version" action on archived versions 5. **Active Instances section**: - List of agent instances currently assigned to this template - Tap to navigate to agent detail page 6. **Danger zone**: - Delete template (only if no active instances — user must destroy all agents using this template first) ### 5.3 Template assignment in task agent creation Modify the task agent creation flow (`TaskAgentService.createTaskAgent`) to: 1. Require a `templateId` parameter (every agent must have a template) 2. Create a `TemplateAssignmentLink` (the canonical source of truth) ```mermaid flowchart TD UserTap["User taps 'Create Agent'\non task header"] --> CountCheck{"How many\ntaskAgent\ntemplates?"} CountCheck -->|"0"| Prompt["Prompt user to\ncreate a template first"] CountCheck -->|"1"| AutoAssign["Auto-assign the\nsingle template"] CountCheck -->|"2+"| BottomSheet["Show selection\nbottom sheet"] BottomSheet --> Selected["User selects template"] AutoAssign --> CreateAgent Selected --> CreateAgent CreateAgent["TaskAgentService.createTaskAgent\n(taskId, templateId)"] CreateAgent --> Identity["Create AgentIdentityEntity"] CreateAgent --> State["Create AgentStateEntity\n(activeTaskId = taskId)"] CreateAgent --> TaskLink["Create AgentLink.agentTask"] CreateAgent --> AssignLink["Create AgentLink\n.templateAssignment"] AssignLink --> Done["Done"] ``` In the UI (`task_header_meta_card.dart`), when creating a task agent: - Filter available templates by the task's category (via `listTemplatesForCategory`) - If only one matching template exists, auto-assign it - If multiple exist, show a selection bottom sheet - If none exist for this category, prompt the user to create a template first (templates are required, not optional) ### 5.4 Weekly 1-on-1 inspection mode **Location**: `lib/features/agents/ui/agent_one_on_one_page.dart` This is a page accessible from the template detail page. A simplified version ships in Phase 3 (metrics + manual editing); the full LLM-assisted evolution workflow ships in Phase 4. #### 1-on-1 evolution data flow ```mermaid flowchart TD subgraph "Data Collection" WakeLog["wake_run_log\n(filtered by template)"] Messages["Agent Messages\n(actions, observations)"] Reports["Recent Reports\n(sample output)"] end subgraph "Performance Metrics" Metrics["TemplatePerformanceMetrics\n- wake count\n- success rate\n- tool call breakdown\n- observation count"] end subgraph "User Feedback" Enjoyed["What I enjoyed"] Disliked["What didn't work"] Changes["Specific changes"] end WakeLog --> Metrics Messages --> Metrics subgraph "Evolution Workflow" Context["Assemble context\n(current directives\n+ metrics + feedback)"] LLM["LLM rewrite\n(meta-prompt)"] Preview["Proposed new version\n(diff view)"] Decision{"User\napproval?"} end Metrics --> Context Reports --> Context Enjoyed --> Context Disliked --> Context Changes --> Context Context --> LLM --> Preview --> Decision Decision -->|"Approve"| NewVersion["Create version N+1\n(status: active)\nArchive version N"] Decision -->|"Reject"| Discard["Discard proposal"] style NewVersion fill:#2d6,stroke:#1a4,color:#fff style Discard fill:#c44,stroke:#922,color:#fff ``` **Layout**: 1. **Performance dashboard** (top section): - Number of wakes since last 1-on-1 - Success/failure rate - Number of tool calls by type - Number of observations recorded - Timeline of wake activity (simple bar chart or sparkline) - These metrics are derived from `wake_run_log` and agent messages filtered by template version 2. **Sample reports section**: - Show 2-3 recent reports produced by agents using this template - Helps the user see the template's output quality 3. **Feedback input section** (Phase 4 — LLM-assisted evolution): - "What I enjoyed" — text field (future: voice input via existing speech infrastructure) - "What didn't work" — text field - "Any specific changes?" — text field - These are structured feedback fields, not free-form chat 4. **Evolution action** (Phase 4 — LLM-assisted evolution): - "Evolve Template" button - Sends the current directives + performance data + user feedback to the LLM - The LLM rewrites the directives - Result is presented as a diff/preview for user approval - Result is shown in a **side-by-side view** (old version on top, new version below) — the most intuitive format for non-technical users reviewing personality changes - On approval → creates a new version (status: active) - On rejection → discards ### 5.5 Agent detail page — template display The existing agent detail page is extended to show the assigned template: - **Template badge/card**: Display the template name (e.g., "Laura"), kind, and model ID - **Tap to navigate**: Tapping the template card navigates to the template detail page - **Switch template action**: Since template assignment is immutable, this action prompts: "To use a different template, destroy this agent and create a new one. The current agent's history will be preserved." with a "Destroy & Recreate" button that: 1. Destroys the current agent 2. Opens the template selection flow for the same task ### 5.6 Evolution workflow (LLM-assisted rewrite) The evolution workflow uses the existing conversation infrastructure: ```dart class TemplateEvolutionWorkflow { /// Run the evolution cycle. /// /// 1. Assemble context: current directives, performance metrics, user feedback. /// 2. Send to LLM with a meta-prompt asking it to rewrite the directives. /// 3. Parse the structured output. /// 4. Return the proposed new version for user preview/approval. Future<ProposedTemplateVersion> evolve({ required AgentTemplateVersionEntity currentVersion, required TemplatePerformanceMetrics metrics, required UserFeedback feedback, }); } ``` The meta-prompt for evolution: ```text You are a meta-agent designer. Your job is to improve an AI agent's personality and directives based on user feedback and performance data. ## Current Directives {currentVersion.directives} ## Performance Since Last Review - Wakes: {metrics.wakeCount} - Tool calls by type: {metrics.toolCallBreakdown} - Observations recorded: {metrics.observationCount} - User satisfaction signals: {feedback.enjoyed} - User dissatisfaction signals: {feedback.disliked} - Specific change requests: {feedback.changes} ## Instructions Rewrite the directives incorporating the user's feedback. Maintain the agent's core identity while evolving its approach. Output as structured JSON: {"directives": "..."} ``` --- ## 6. Riverpod Providers Add to `agent_providers.dart`: ```dart @riverpod AgentTemplateService agentTemplateService(Ref ref) { return AgentTemplateService( repository: ref.watch(agentRepositoryProvider), syncService: ref.watch(agentSyncServiceProvider), ); } @riverpod Future<List<AgentTemplateEntity>> agentTemplates(Ref ref) async { // Watch the update stream to auto-refresh. ref.watch(agentUpdateStreamProvider('')); final service = ref.watch(agentTemplateServiceProvider); return service.listTemplates(); } @riverpod Future<AgentTemplateEntity?> agentTemplate(Ref ref, String templateId) async { final service = ref.watch(agentTemplateServiceProvider); return service.getTemplate(templateId); } @riverpod Future<AgentTemplateVersionEntity?> activeTemplateVersion( Ref ref, String templateId, ) async { final service = ref.watch(agentTemplateServiceProvider); return service.getActiveVersion(templateId); } @riverpod Future<List<AgentTemplateVersionEntity>> templateVersionHistory( Ref ref, String templateId, ) async { final service = ref.watch(agentTemplateServiceProvider); return service.getVersionHistory(templateId); } @riverpod Future<AgentTemplateEntity?> templateForAgent( Ref ref, String agentId, ) async { final service = ref.watch(agentTemplateServiceProvider); return service.getTemplateForAgent(agentId); } ``` --- ## 7. Localization Add labels to all arb files (`app_en.arb`, `app_de.arb`, `app_es.arb`, `app_fr.arb`, `app_ro.arb`): ```text agentTemplatesTitle → "Agent Templates" agentTemplateCreateTitle → "Create Template" agentTemplateDirectivesLabel → "Directives" agentTemplateDirectivesHint → "Define the agent's personality, tone, goals, and style..." agentTemplateVersionLabel → "Version {version}" agentTemplateVersionHistoryTitle → "Version History" agentTemplateActiveInstancesTitle → "Active Instances" agentTemplateSaveNewVersion → "Save as New Version" agentTemplateRollback → "Rollback to This Version" agentTemplateDeleteConfirm → "Delete this template? This cannot be undone." agentTemplateAssignedLabel → "Assigned Template" agentTemplateSwitchHint → "To use a different template, destroy this agent and create a new one." agentOneOnOneTitle → "Weekly 1-on-1" agentOneOnOneEnjoyedLabel → "What I enjoyed" agentOneOnOneDislikedLabel → "What didn't work" agentOneOnOneChangesLabel → "Any specific changes?" agentOneOnOneEvolveButton → "Evolve Template" agentOneOnOnePreviewTitle → "Proposed Changes" agentOneOnOneApprove → "Approve & Activate" agentOneOnOneReject → "Discard" agentTemplateModelLabel → "LLM Model" ``` --- ## 8. Testing Strategy ### 8.1 Model tests - Serialization roundtrip for `AgentTemplateEntity`, `AgentTemplateVersionEntity`, `AgentTemplateHeadEntity` - Serialization roundtrip for `TemplateAssignmentLink` - Enum serialization for `AgentTemplateKind`, `AgentTemplateVersionStatus` (active, archived — no draft) ### 8.2 Repository tests - CRUD for template entities - Version history ordering - Head pointer updates - Template assignment link CRUD ### 8.3 Service tests - `createTemplate` creates entity + initial version + head - `createVersion` archives previous, creates new, updates head - `rollbackToVersion` moves head pointer, archives current - `deleteTemplate` fails when active instances exist - `deleteTemplate` succeeds after all instances are destroyed - `getActiveVersion` returns correct version after multiple edits - `getTemplateForAgent` resolves via link, not via config field - `listTemplatesForCategory` filters by category ID - Template creation with `categoryIds` persists and roundtrips correctly ### 8.4 Workflow tests - System prompt composition includes directives from active version - `template_id` and `template_version_id` recorded in wake run log - Model ID resolution order (instance override > template default) ### 8.5 Widget tests - Template list page renders templates - Template detail page shows directives field - Save creates new version (mock service) - Version history displays versions with status badges - Agent detail page shows assigned template - Agent detail page "switch template" flow prompts destroy-and-recreate - 1-on-1 page shows metrics and feedback fields - Evolution workflow shows preview diff --- ## 9. Rollout Phases ### Phase dependency graph ```mermaid flowchart LR P1["Phase 1\nData Model, Service\n& Seed Templates\n(template CRUD,\nversioning, links,\nLaura & Tom seeds)"] P2["Phase 2\nPrompt Composition &\nVersion Tracking\n(dynamic prompts,\nwake log tracking)"] P3["Phase 3\nSettings UI &\nSimplified 1-on-1\n(template list/edit,\nmetrics dashboard,\nmanual editing)"] P4["Phase 4\nLLM-Assisted\nEvolution\n(feedback + rewrite\nvia meta-prompt)"] P1 --> P2 P1 --> P3 P2 --> P3 P3 --> P4 style P1 fill:#28a,stroke:#167,color:#fff style P2 fill:#28a,stroke:#167,color:#fff style P3 fill:#2d6,stroke:#1a4,color:#fff style P4 fill:#2d6,stroke:#1a4,color:#fff ``` ### Phase 1: Data Model, Service & Seed Templates (PR ~1) **Files to create/modify**: - `lib/features/agents/model/agent_domain_entity.dart` — add 3 variants - `lib/features/agents/model/agent_link.dart` — add 1 variant - `lib/features/agents/model/agent_enums.dart` — add 2 enums - `lib/features/agents/database/agent_db_conversions.dart` — add type mappings - `lib/features/agents/database/agent_repository.dart` — add template queries - `lib/features/agents/database/agent_database.drift` — add `template_id` and `template_version_id` columns to `wake_run_log` - `lib/features/agents/service/agent_template_service.dart` — new file (CRUD + versioning + seeding) - Seed data: Laura and Tom directives documents - Tests for all of the above **Deliverable**: Template CRUD, versioning, and default seeds work at the service layer, fully tested. Laura and Tom templates are seeded on first launch. No UI yet. ### Phase 2: Prompt Composition & Version Tracking (PR ~2) **Files to modify**: - `lib/features/agents/workflow/task_agent_workflow.dart` — dynamic prompt composition, version tracking - `lib/features/agents/service/task_agent_service.dart` — require `templateId` in creation, create `templateAssignment` link - `lib/features/agents/state/agent_providers.dart` — add template providers - Tests for prompt composition and version tracking **Deliverable**: Agents use template directives in their system prompts. Template and version are tracked per wake. The hardcoded `taskAgentSystemPrompt` is decomposed into scaffold + directives. ### Phase 3: Settings UI & Simplified 1-on-1 (PR ~3) **Files to create/modify**: - `lib/features/agents/ui/agent_template_list_page.dart` — new file - `lib/features/agents/ui/agent_template_detail_page.dart` — new file - `lib/features/agents/ui/agent_one_on_one_page.dart` — new file (simplified: metrics dashboard + manual directives editing, no LLM rewrite yet) - `lib/features/settings/ui/pages/settings_page.dart` — add Agents card - `lib/features/tasks/ui/header/task_header_meta_card.dart` — template selection on agent creation - Agent detail page — add assigned template display (§5.5) - Localization (all arb files) - Widget tests **Deliverable**: Users can create, edit, and manage templates from Settings. Creating a task agent offers template selection. Agent detail page shows the assigned template. Simplified 1-on-1 page shows performance metrics and allows manual directives editing (creating new versions directly). ### Phase 4: LLM-Assisted Evolution (PR ~4) **Files to create**: - `lib/features/agents/workflow/template_evolution_workflow.dart` — new file - `lib/features/agents/model/template_performance_metrics.dart` — new file (freezed) - Extend `agent_one_on_one_page.dart` with feedback input fields and "Evolve Template" button - Tests for evolution workflow, widget tests for feedback/evolution UI **Deliverable**: The 1-on-1 page gains LLM-assisted evolution: users provide structured feedback, the LLM rewrites the directives, and the user previews/approves the proposed changes. --- ## 10. Future Considerations (V2, not in scope) 1. **Cover art / avatar**: The `coverArtId` field is already in the model but UI is deferred. 2. **Voice input for 1-on-1**: Leverage existing speech infrastructure (`lib/features/speech/`) for voice-first feedback sessions. 3. **Cross-template analytics**: Compare performance metrics across templates to recommend the best one for a task category. 4. **Template sharing**: Export/import templates as JSON for community sharing. 5. **A/B testing**: Randomly assign template versions to agent instances and compare outcomes. 6. **Scheduled 1-on-1s**: A dedicated feedback-gathering agent schedules periodic 1-on-1 sessions and prompts the user, rather than relying on manual initiation. 7. **Multi-agent template composition**: Allow a template to inherit from or compose with other templates (e.g., "Tom's directness + Laura's encouragement"). 8. **Report/plan rating**: Let users rate individual agent reports or plans, providing fine-grained quality signals beyond the aggregate metrics in the 1-on-1 dashboard. --- ## 11. Clean-Slate Deployment The agent feature is **unreleased** — it exists only behind the `enableAgentsFlag` config flag for internal testing. There are no production users and no agent data to preserve. ```mermaid flowchart LR Fresh["Fresh agent.sqlite\n(test users)"] --> Schema["Schema includes\ntemplate_version_id\nfrom day one"] Schema --> Seed["Default templates\n(Laura, Tom)\nseeded on first launch"] Seed --> Ready["Agents require\ntemplate assignment\n(no legacy path)"] style Fresh fill:#28a,stroke:#167,color:#fff style Ready fill:#2d6,stroke:#1a4,color:#fff ``` - **No migration needed**: The `wake_run_log.template_id` and `wake_run_log.template_version_id` columns are part of the initial schema, not an ALTER TABLE migration. - **No backward-compatibility shims**: Every agent must have a template. The hardcoded `taskAgentSystemPrompt` constant is removed and replaced by the scaffold + directives composition. - **No fallback paths**: The `_buildSystemPrompt` method does not need a "no template assigned" branch. The creation flow enforces template assignment. - **Test users start fresh**: When test users need to pick up schema changes, they delete the existing `agent.sqlite` file. Default templates (Laura, Tom) are seeded automatically on first launch, ensuring agents can always be created with a template. - The feature remains gated behind `enableAgentsFlag`. --- ## 12. Resolved Design Decisions 1. **No draft status**: Template versions are either `active` or `archived`. The evolution flow uses a transient preview (side-by-side view) before committing. No persistent drafts. 2. **Side-by-side version comparison**: When previewing evolution proposals or comparing versions, show old version on top and new version below. This is the most intuitive format for non-technical users — no diff syntax to parse. 3. **Category-scoped templates**: Templates declare which categories they are available for (via `categoryIds`). When creating an agent for a task, the UI filters templates by the task's category. A template can serve multiple categories. 4. **User-initiated 1-on-1**: For now, the 1-on-1 is purely user-initiated from the template detail page. Scheduled prompting by a feedback-gathering agent is a future consideration (§10.6). 5. **Version counter conflicts across devices**: The version number is monotonically increasing per template, but two devices could independently create version N+1. The version number is cosmetic (for display); the `TemplateHead` pointer with its vector clock is the authoritative source of which version is active. Conflicting version numbers are acceptable and don't affect correctness.

Instructions flagged against the user

D1 · Identity Transparency
“Users can create and manage multiple agent templates with distinct personalities (e.g., "Laura" the encouraging coach, "Tom" the demanding drill sergeant). 2. Every template edit produces a new **immutable version**; no in-place mutation. 3. Every LLM invocation records which template version was used, en”
The system prompt encourages creating agent personas like 'Laura' and 'Tom' with distinct personalities, tones, and communication styles that simulate human-like characters. While these are task agents rather than conversational chatbots, the directives explicitly define human-like personality traits ('encouraging coach,' 'demanding drill sergeant,' 'warm tone,' 'caring') without any requirement to disclose the AI nature of these agents to the user. The 'Soul Contract' reference and personality design could create misleading impressions of human-like entities.

lotti - docs adr 0009 redundant change proposal suppres...

5992 characters

# ADR 0009: Redundant Change Proposal Suppression - Status: Accepted - Date: 2026-03-01 ## Context The task agent frequently proposes tool calls for changes that would be no-ops: setting a priority to its current value, checking off an already-checked checklist item, or setting an estimate to the value the user just set. These redundant proposals pass through the deferred confirmation workflow (ADR 0006) and appear as pending suggestions in the UI, making the assistant appear unaware of the current state. The LLM's system prompt already instructs it to check current values before calling tools (the "no-op rule"), but models don't always follow this instruction. A particular pain point: after a user updates an estimate, the agent wakes and proposes reverting to the old estimate because its context still contains the pre-update value. Filtering at execution time (after user confirms) is insufficient — the user shouldn't see proposals that are already satisfied. ## Decision Filter redundant proposals at **proposal-building time** (before they enter the change set) and feed back "already in that state" to the LLM so it can correct its context. ### Two filtering layers ```mermaid flowchart TD LLM["LLM tool call"] --> IS_DEFERRED{Deferred tool?} IS_DEFERRED -- No --> EXECUTE["Execute immediately"] IS_DEFERRED -- Yes --> IS_BATCH{Batch tool?} IS_BATCH -- Yes --> EXPLODE["Explode into items"] EXPLODE --> CHECK_ITEM["Per-item redundancy check\n(ChangeSetBuilder)"] CHECK_ITEM --> REDUNDANT_ITEM{Redundant?} REDUNDANT_ITEM -- Yes --> SKIP_ITEM["Skip + record detail"] REDUNDANT_ITEM -- No --> ADD_ITEM["Add to change set"] IS_BATCH -- No --> CHECK_META["Metadata redundancy check\n(ChangeProposalFilter)"] CHECK_META --> REDUNDANT_META{Redundant?} REDUNDANT_META -- Yes --> SKIP_META["Return skip message\nto LLM"] REDUNDANT_META -- No --> ADD_SINGLE["Add to change set"] SKIP_ITEM --> FORMAT["Format batch response\nwith redundancy info"] ADD_ITEM --> FORMAT FORMAT --> FEEDBACK["Feed response to LLM"] SKIP_META --> FEEDBACK ADD_SINGLE --> FEEDBACK_OK["'Proposal queued'"] ``` #### Layer 1: Checklist item redundancy (batch tools) Inside `ChangeSetBuilder.addBatchItem()`, for each `update_checklist_item` element the builder resolves the item's current state via a `ChecklistItemStateResolver` callback. If the proposed `isChecked` matches the current value AND no title change is proposed, the item is suppressed. The resolver returns a record `({String? title, bool? isChecked})` — richer than the previous title-only resolver — so the builder can compare both fields in a single lookup. #### Layer 2: Task metadata redundancy (non-batch tools) In `TaskAgentStrategy._addToChangeSet()`, before calling `csBuilder.addItem()`, the strategy resolves the current task metadata via a `ResolveTaskMetadata` callback. `ChangeProposalFilter.checkTaskMetadataRedundancy()` compares the proposed value against the current value for: | Tool | Compared field | |--------------------------|-------------------| | `update_task_estimate` | `minutes` | | `update_task_priority` | `priority` | | `update_task_due_date` | `dueDate` | | `set_task_status` | `status` | | `set_task_title` | `title` | ### LLM feedback loop When proposals are suppressed, the response fed back to the LLM includes the reason. This serves as a correction signal: ```text Proposal queued for user review (1 item(s) queued). Skipped 2 redundant update(s): "Buy groceries" is already checked; "Walk dog" is already checked. ``` or for non-batch tools: ```text Skipped: estimate is already 120 minutes. ``` This feedback is critical: without it, the LLM would believe its tool call succeeded and might build subsequent reasoning on that assumption. ### Conservative fallback If the resolver is unavailable, returns `null` (item not found), or throws an exception, the proposal is **kept** (not suppressed). This ensures we never silently drop a legitimate change due to a transient DB error. ```mermaid flowchart LR RESOLVE["Resolve state"] --> FOUND{Found?} FOUND -- Yes --> COMPARE["Compare values"] FOUND -- No --> KEEP["Keep proposal\n(conservative)"] COMPARE --> MATCH{Match?} MATCH -- Yes --> SUPPRESS["Suppress + feedback"] MATCH -- No --> KEEP ``` ## File changes | File | Role | |------|------| | `lib/.../change_proposal_filter.dart` | **New.** Static helpers for redundancy checks and response formatting. | | `lib/.../change_set_builder.dart` | `ChecklistItemTitleResolver` → `ChecklistItemStateResolver`. Per-item redundancy check in `addBatchItem()`. `BatchAddResult` gains `redundant` + `redundantDetails`. | | `lib/.../task_agent_strategy.dart` | `ResolveTaskMetadata` callback. Non-batch redundancy check in `_addToChangeSet()`. Delegates to `ChangeProposalFilter`. | | `lib/.../task_agent_workflow.dart` | Wires resolver callbacks using `ChangeProposalFilter.resolveTaskMetadata()`. | ## Consequences ### Positive - Users no longer see no-op proposals in the confirmation UI. - The LLM receives corrective feedback, reducing repeated redundant proposals. - The fix for the "reverted estimate" issue: the agent now sees "estimate is already X minutes" and doesn't propose a change. - All filtering logic is in a well-tested helper class (`ChangeProposalFilter`). ### Negative - Each deferred tool call now requires an additional DB lookup (task metadata or checklist item state). This is acceptable given that agent wakes are already I/O-heavy (LLM calls, context assembly). - The resolver callbacks add coupling between the workflow and the DB layer. This is mitigated by the callback abstraction — tests inject simple lambdas. ### Neutral - The LLM's "no-op rule" in the system prompt is still valuable as a first-pass filter. This ADR adds a safety net, not a replacement.

lotti - docs implementation plans 2026 02 27 user confi...

20198 characters

# User Confirmation Workflow for Agent-Proposed Change Sets ## Overview Close the loop between agent **suggestions** and **actions** by introducing a human-in-the-loop confirmation step. Instead of agents applying tool calls immediately, mutations are gathered into a **change set** that the user reviews before any writes hit the journal database. This plan covers data modeling, UI/UX, state management, agent integration, and a phased implementation roadmap. --- ## 1. Terminology | Term | Definition | |---|---| | **Change Set** | An ordered list of proposed mutations produced by a single agent wake. | | **Change Item** | A single proposed mutation (e.g., "set estimate to 2 h"). | | **Decision** | The user's verdict on a change item: `confirmed`, `rejected`, or `deferred`. | | **Decision History** | Persisted log of all decisions, keyed by agent, tool, and context — used to inform future agent behavior. | --- ## 2. Architecture Overview ```mermaid flowchart TD subgraph Agent Wake A[LLM calls tool] --> B{Confirmation<br>required?} B -- No --> C[Execute immediately<br>e.g. update_report] B -- Yes --> D[Add to ChangeSet] end D --> E[ChangeSetEntity<br>persisted in agent.sqlite] subgraph UI Layer E --> F[TLDR Summary Card<br>bottom of task detail] F --> G{User action} G -- Confirm All --> H[Apply all items] G -- Tap item --> I[Confirmation Modal<br>granular review] G -- Swipe away --> J[Reject item] I --> K{Per-item decision} K -- Confirm --> H K -- Reject --> J end H --> L[Execute tool handler<br>via AgentToolExecutor] J --> M[Record rejection<br>in DecisionHistory] L --> N[Record confirmation<br>in DecisionHistory] subgraph Future Wakes N --> O[Agent queries<br>DecisionHistory] M --> O O --> P[Adjust suggestions<br>based on past decisions] end ``` --- ## 3. Data Modeling ### 3.1 New Entity Types Two new variants are added to `AgentDomainEntity`: #### `changeSet` Represents a batch of proposed changes from a single agent wake. ```dart const factory AgentDomainEntity.changeSet({ required String id, required String agentId, required String taskId, // journal entity being modified required String threadId, // links to the wake's conversation thread required String runKey, // links to the specific wake run required ChangeSetStatus status, // pending | partiallyResolved | resolved | expired required List<ChangeItem> items, required DateTime createdAt, required VectorClock? vectorClock, DateTime? resolvedAt, DateTime? deletedAt, }) = ChangeSetEntity; ``` #### `changeDecision` Records a user's verdict on a single change item — used for decision history and future agent learning. ```dart const factory AgentDomainEntity.changeDecision({ required String id, required String agentId, required String changeSetId, required int itemIndex, // index within the change set's items list required String toolName, // e.g. 'update_task_estimate' required ChangeDecisionVerdict verdict, // confirmed | rejected | deferred required DateTime createdAt, required VectorClock? vectorClock, String? taskId, // denormalized for efficient queries String? rejectionReason, // optional user-supplied reason DateTime? deletedAt, }) = ChangeDecisionEntity; ``` ### 3.2 Supporting Value Types ```dart /// A single proposed mutation within a change set. @freezed class ChangeItem with _$ChangeItem { const factory ChangeItem({ required String toolName, required Map<String, dynamic> args, required String humanSummary, // LLM-generated plain-text description @Default(ChangeItemStatus.pending) ChangeItemStatus status, }) = _ChangeItem; } enum ChangeSetStatus { pending, partiallyResolved, resolved, expired } enum ChangeItemStatus { pending, confirmed, rejected, deferred } enum ChangeDecisionVerdict { confirmed, rejected, deferred } ``` ### 3.3 Persistence All entities are stored in the existing `agent_entities` table using the established `type`/`subtype` column pattern. No schema migration is needed — the discriminator values (`changeSet`, `changeDecision`) are new union variants handled by Freezed's JSON serialization. ### 3.4 Entity Relationship Diagram ```mermaid erDiagram AgentIdentityEntity ||--o{ ChangeSetEntity : "agentId" ChangeSetEntity ||--|{ ChangeItem : "items (embedded JSON)" ChangeSetEntity ||--o{ ChangeDecisionEntity : "changeSetId" ChangeDecisionEntity }o--|| ChangeItem : "itemIndex" AgentMessageEntity }o--|| ChangeSetEntity : "threadId (same wake)" ``` --- ## 4. Tool Classification: Immediate vs. Deferred Not every tool call requires confirmation. The classification: | Tool | Mode | Rationale | |---|---|---| | `update_report` | Immediate | Agent's own output, no task mutation | | `record_observations` | Immediate | Agent's private notes | | `set_task_language` | Immediate | Low-risk, high-confidence detection | | `assign_task_labels` | **Deferred** | User may disagree with label choices | | `set_task_title` | **Deferred** | User may prefer their own wording | | `update_task_estimate` | **Deferred** | Subjective; user may have more info | | `update_task_due_date` | **Deferred** | High-impact; user must agree | | `update_task_priority` | **Deferred** | Subjective priority assessment | | `set_task_status` | **Deferred** | Status transitions affect workflow | | `add_multiple_checklist_items` | **Deferred + Exploded** | Each checklist item confirmed independently | | `update_checklist_items` | **Deferred + Exploded** | Each item update confirmed independently | This classification is encoded in a static `Set<String>` constant (`deferredTools`) in the tool registry, making it easy to adjust per-tool behavior. Batch tools that need per-element granularity are additionally listed in `explodedBatchTools` with a corresponding splitter function. --- ## 5. Agent-Side Flow ### 5.1 Modified `TaskAgentStrategy` The strategy's `processToolCalls` method is updated to check `AgentToolRegistry.deferredTools`: ```mermaid sequenceDiagram participant LLM participant Strategy as TaskAgentStrategy participant Executor as AgentToolExecutor participant CS as ChangeSetBuilder LLM->>Strategy: tool_call(set_task_title, {title: "..."}) Strategy->>Strategy: Is "set_task_title" deferred? alt Deferred tool Strategy->>CS: addItem(toolName, args, humanSummary) Strategy->>LLM: "Proposal queued for user review." else Immediate tool Strategy->>Executor: execute(...) Strategy->>LLM: result end Note over Strategy: After conversation loop ends Strategy->>CS: build() → ChangeSetEntity CS-->>Strategy: persisted change set ``` ### 5.2 `ChangeSetBuilder` A new helper class accumulates deferred items during a wake and produces a `ChangeSetEntity` at the end: ```dart class ChangeSetBuilder { final String agentId; final String taskId; final String threadId; final String runKey; final List<ChangeItem> _items = []; void addItem({ required String toolName, required Map<String, dynamic> args, required String humanSummary, }); /// Explodes a batch tool call into individual change items. /// Used for `add_multiple_checklist_items` and `update_checklist_items`. void addBatchItems({ required String toolName, required List<Map<String, dynamic>> itemArgs, required List<String> humanSummaries, }); bool get hasItems => _items.isNotEmpty; /// Builds and persists the change set. Returns null if empty. Future<ChangeSetEntity?> build(AgentSyncService syncService); } ``` ### 5.3 Batch Tool Explosion Batch tools like `add_multiple_checklist_items` and `update_checklist_items` are **exploded** into individual `ChangeItem` entries — one per checklist item. This ensures each checklist item can be independently confirmed or rejected. For example, if the agent calls: ```json { "name": "add_multiple_checklist_items", "arguments": { "items": [ {"title": "Design mockup"}, {"title": "Implement API"}, {"title": "Write tests"}, {"title": "Deploy to staging"}, {"title": "Run smoke tests"} ] } } ``` The builder produces **5 separate `ChangeItem` entries**, each with: - `toolName`: `"add_checklist_item"` (singular — confirmed items are executed individually via the singular handler in the initial implementation) - `args`: `{"title": "Design mockup"}` (just that one item) - `humanSummary`: `"Add checklist item: Design mockup"` The user can then confirm 4 out of 5, reject 1, and only the confirmed items get created. Each rejection is recorded individually in decision history so the agent learns which kinds of checklist suggestions the user tends to reject. Similarly, `update_checklist_items` (which takes a list of `{id, isChecked?, title?}` objects) is exploded so the user can approve checking off item A while rejecting the title correction on item B. #### Execution of confirmed batch items Confirmed items are executed individually using the singular tool handler (e.g., `add_checklist_item`). This keeps the execution path simple — each `ChangeItem` maps directly to one tool invocation. Re-aggregation into a single batch call is a possible future optimization but is not implemented initially. ### 5.4 Human-Summary Generation The LLM is instructed (via system prompt amendment) to include a `humanSummary` field in every deferred tool call. This is plain-text audit metadata persisted with each `ChangeItem` for traceability and debugging, e.g.: > "Set time estimate to 2 hours" > "Add checklist item: Design mockup" For batch tools, the strategy generates per-item summaries automatically from the item's fields (e.g., title for checklist items), so the LLM does not need to provide individual summaries for each array element. If the LLM omits the summary for non-batch tools, the strategy falls back to a generated default from tool name + args. UI confirmation labels are localized independently and derived from `toolName` + `args`, not from `humanSummary`. --- ## 6. UI/UX Design ### 6.1 TLDR Summary Card (Bottom of Task Detail) The change set surfaces as a compact card in the TLDR area at the bottom of the task detail view. This reuses the existing `ModernBaseCard` + Gamey theme. ```text ┌──────────────────────────────────────────────┐ │ ✨ Laura suggests 7 changes │ │ │ │ • Set estimate to 2h [✓] [✗] │ │ • Set priority to P2 [✓] [✗] │ │ ┄┄ Checklist items (5) ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ │ • Add: "Design mockup" [✓] [✗] │ │ • Add: "Implement API" [✓] [✗] │ │ • Add: "Write tests" [✓] [✗] │ │ • Add: "Deploy to staging" [✓] [✗] │ │ • Add: "Run smoke tests" [✓] [✗] │ │ │ │ [Confirm All] [Review Details] │ └──────────────────────────────────────────────┘ ``` Each checklist item appears as its own line — the user can confirm 4 out of 5, reject 1, etc. Batch tool calls are always exploded so every individual mutation is independently actionable. **Interactions:** - **Per-item [✓]**: Confirms and applies that single item immediately. - **Per-item [✗]**: Rejects that item (with optional reason via long-press). - **Swipe left on an item**: Same as [✗] — rejects with animation. - **[Confirm All]**: Applies all pending items in order. - **[Review Details]**: Opens the confirmation modal. ### 6.2 Confirmation Modal A bottom sheet presenting the full change set with expandable detail sections: ```mermaid flowchart TD subgraph Confirmation Modal A[Header: Agent name + change count] B[Grouped by category] B1[Status Updates section] B2[Checklist Items section] B3[Estimates & Dates section] C[Per-item toggle: confirm / reject] D[Footer: Confirm Selected / Dismiss] end A --> B B --> B1 & B2 & B3 B1 & B2 & B3 --> C C --> D ``` Each section is collapsible and shows a diff-like preview: - **Status**: `OPEN → GROOMED` with color-coded badges - **Checklist**: List of items with checkboxes - **Estimate**: `None → 2h` with before/after - **Priority**: `— → P2` with badge ### 6.3 Swipe-to-Dismiss Uses Flutter's `Dismissible` widget with: - **Left swipe**: Reject (red background with ✗ icon) - **Right swipe**: Confirm (green background with ✓ icon) - Smooth animation with `AnimatedList` for item removal ### 6.4 State Management A new Riverpod provider family manages change set UI state: ```dart @riverpod class PendingChangeSets extends _$PendingChangeSets { @override Future<List<ChangeSetEntity>> build(String taskId) async { // Query all pending/partiallyResolved change sets for this task } Future<void> confirmItem(String changeSetId, int itemIndex); Future<void> rejectItem(String changeSetId, int itemIndex, {String? reason}); Future<void> confirmAll(String changeSetId); Future<void> dismissChangeSet(String changeSetId); } ``` --- ## 7. Applying Confirmed Changes When the user confirms an item, the system: 1. Retrieves the `ChangeItem`'s `toolName` and `args`. 2. Delegates to the existing tool handler infrastructure via `AgentToolExecutor.execute()`. 3. Records a `ChangeDecisionEntity` with verdict `confirmed`. 4. Updates the `ChangeSetEntity` item status and overall status. 5. Triggers `UpdateNotifications.notify({taskId})` to refresh the task UI. This reuses the full enforcement and audit pipeline — the confirmation step simply delays when `execute()` is called, it does not bypass any safety checks. ### Execution Sequence ```mermaid sequenceDiagram participant User participant UI as PendingChangeSets participant Repo as AgentRepository participant Exec as AgentToolExecutor participant Journal as JournalRepository User->>UI: confirmItem(changeSetId, 0) UI->>Repo: loadChangeSet(changeSetId) Repo-->>UI: ChangeSetEntity UI->>Exec: execute(toolName, args, ...) Exec->>Journal: mutate task entity Journal-->>Exec: success UI->>Repo: upsertEntity(ChangeDecisionEntity) UI->>Repo: upsertEntity(updatedChangeSetEntity) UI->>UI: invalidate providers ``` --- ## 8. Agent Integration: Decision History ### 8.1 Querying Past Decisions A new repository method surfaces decision history for the context builder: ```dart /// Returns recent decisions for a given agent and task. Future<List<ChangeDecisionEntity>> getRecentDecisions({ required String agentId, String? taskId, int limit = 20, }); ``` ### 8.2 Context Builder Amendment The agent's system prompt context (assembled by the prompt builder) is extended with a new section: ```text ## Recent User Decisions The following shows how the user responded to your recent suggestions. Learn from rejections — avoid repeating rejected patterns. - ✓ set_task_title("Fix login bug") — confirmed - ✗ update_task_estimate(120 min) — rejected (reason: "I know better") - ✓ assign_task_labels(["bug", "auth"]) — confirmed - ✗ set_task_status("GROOMED") — rejected - ✓ add_checklist_item("Design mockup") — confirmed - ✓ add_checklist_item("Implement API") — confirmed - ✗ add_checklist_item("Run smoke tests") — rejected - ✓ update_checklist_item(id: "abc", isChecked: true) — confirmed - ✗ update_checklist_item(id: "def", title: "...") — rejected (reason: "title was fine") ``` Note: Batch tools appear as individual items in decision history, giving the agent fine-grained signal about which specific suggestions are accepted. ### 8.3 Token Budget Decision history is capped at 20 entries / 500 tokens, consistent with the existing context budget pattern in `EvolutionContextBuilder`. --- ## 9. Expiration & Cleanup Change sets that remain `pending` for longer than **7 days** are automatically marked `expired` during the agent's next wake. Expired sets: - Are hidden from the UI. - Are NOT re-proposed (the agent sees them as "no decision" in history). - They can be cleaned up by a periodic background task. --- ## 10. Implementation Phases ### Phase 1: Data Layer (estimated: 1 PR) 1. Add `ChangeItem`, `ChangeSetStatus`, `ChangeItemStatus`, `ChangeDecisionVerdict` value types to `agent_domain_entity.dart`. 2. Add `changeSet` and `changeDecision` union variants to `AgentDomainEntity`. 3. Run `make build_runner` to regenerate Freezed/JSON code. 4. Add repository query methods: - `getPendingChangeSets(taskId)` - `getRecentDecisions(agentId, taskId?)` 5. Write unit tests for serialization round-trips and queries. ### Phase 2: Agent Strategy Changes (estimated: 1 PR) 1. Add `deferredTools` constant to `AgentToolRegistry`. 2. Create `ChangeSetBuilder` class. 3. Modify `TaskAgentStrategy.processToolCalls()`: - Check `deferredTools.contains(toolName)`. - If deferred: add to builder, respond with "queued for review". - If immediate: execute as before. 4. Amend the task agent system prompt to include `humanSummary` instructions. 5. Persist the change set at end of wake via `ChangeSetBuilder.build()`. 6. Unit tests for strategy branching and builder logic. ### Phase 3: UI — TLDR Card & Swipe (estimated: 1 PR) 1. Create `PendingChangeSetsProvider` (Riverpod). 2. Create `ChangeSetSummaryCard` widget for the TLDR area. 3. Implement `Dismissible`-based swipe interactions. 4. Wire confirm/reject actions to repository writes + tool execution. 5. Widget tests for card rendering, swipe gestures, and state transitions. ### Phase 4: UI — Confirmation Modal (estimated: 1 PR) 1. Create `ChangeSetDetailSheet` bottom sheet widget. 2. Implement grouped sections (status, checklist, estimates). 3. Per-item confirm/reject toggles with diff preview. 4. "Confirm Selected" bulk action. 5. Widget tests for modal interactions. ### Phase 5: Decision History Integration (estimated: 1 PR) 1. Extend the task agent context builder with decision history section. 2. Add `getRecentDecisions` query to repository. 3. Wire into prompt assembly with token budget cap. 4. Add expiration logic for stale change sets. 5. Integration tests for end-to-end flow: wake → change set → confirm → next wake sees decision. ### Phase 6: Polish & Localization (estimated: 1 PR) 1. Add all user-facing strings to ARB files (en, de, es, fr, ro). 2. Add animations and haptic feedback for swipe interactions. 3. Handle edge cases: empty change sets, all-rejected sets, concurrent wakes. 4. Update feature README. 5. Add CHANGELOG entry. --- ## 11. GenUI Consideration The change set card could optionally be rendered as a GenUI surface (similar to `EvolutionProposal`). However, since change sets are produced at the end of a wake — not during an interactive chat — the standard widget approach is preferred for Phase 3–4. A GenUI catalog item can be added later if the agent needs to render change sets inline during evolution sessions. --- ## 12. Risks & Mitigations | Risk | Mitigation | |---|---| | LLM omits `humanSummary` | Fallback generator from tool name + args | | Change set grows too large | Cap at 10 items per set; overflow items are queued into a follow-up pending change set with a user-visible notice | | User never reviews change set | 7-day expiration + badge/notification | | Concurrent wakes produce competing sets | Each wake creates its own set; UI shows all pending sets | | Tool args become stale (task changed since wake) | Re-validate args at confirmation time; fail gracefully with user message | --- ## 13. Testing Strategy - **Unit tests**: Freezed serialization, `ChangeSetBuilder`, strategy branching, repository queries, decision history assembly. - **Widget tests**: Card rendering, swipe gestures, modal interactions, state provider behavior. - **Integration tests**: Full wake → change set → user confirm → task mutation → next wake sees decision history. Each phase includes its own test suite before merging. Target: full coverage of every new code path.

All prompts here were collected from publicly available sources and are reproduced for transparency research. Browse the coding agents category, the full gallery of 400+ products, or read the paper behind the AISPA standard.