|
| 1 | +# Private Notes Spec |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +`private-notes` is a voice-first personal note-taking agent for OpenHome. |
| 6 | + |
| 7 | +- save a note |
| 8 | +- read one or more notes |
| 9 | +- overwrite a specific note |
| 10 | +- delete one or more notes |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Core Principles |
| 15 | + |
| 16 | +1. Notes are private user data stored in JSON. |
| 17 | +2. The LLM picks which tool to call. Python executes it. |
| 18 | +3. Tool execution is id-based, not title-based. |
| 19 | +4. Voice responses are short and natural. |
| 20 | +5. No open-ended agent loop. Capped at 4 turns. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Architecture |
| 25 | + |
| 26 | +A uniform tool loop with one system prompt and conversation history: |
| 27 | + |
| 28 | +```text |
| 29 | +history = [user: initial context] |
| 30 | +
|
| 31 | +while turns remain: |
| 32 | + tool_call = LLM(history, SYSTEM_PROMPT) |
| 33 | + history += assistant: tool_call |
| 34 | +
|
| 35 | + finish -> speak response, stop |
| 36 | + ask_followup -> speak question, history += user: answer, continue |
| 37 | + write/read/delete -> execute in Python, history += user: result, continue |
| 38 | +``` |
| 39 | + |
| 40 | +One system prompt. One conversation via `history`. `finish` is a tool like any other. The LLM writes confirmation messages for destructive actions. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Data Model |
| 45 | + |
| 46 | +### Note |
| 47 | + |
| 48 | +```json |
| 49 | +{ |
| 50 | + "id": "uuid", |
| 51 | + "title": "string", |
| 52 | + "content": "string", |
| 53 | + "created_at": "ISO timestamp", |
| 54 | + "updated_at": "ISO timestamp" |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +### Store |
| 59 | + |
| 60 | +```json |
| 61 | +{ |
| 62 | + "schema_version": 2, |
| 63 | + "notes": [Note] |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +Persistent storage lives in `private_notes.json`. |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## Tools |
| 72 | + |
| 73 | +### `write_note` |
| 74 | + |
| 75 | +```json |
| 76 | +{"name": "write_note", "arguments": {"note_id": null, "title": "string", "content": "string", "confirmation": "string or null"}} |
| 77 | +``` |
| 78 | + |
| 79 | +- `note_id = null` creates a new note (no confirmation needed). |
| 80 | +- `note_id = <uuid>` overwrites an existing note. LLM provides the `confirmation` prompt. |
| 81 | +- If `note_id` does not exist, Python returns an error result instead of crashing the ability. |
| 82 | + |
| 83 | +### `read_notes` |
| 84 | + |
| 85 | +```json |
| 86 | +{"name": "read_notes", "arguments": {"note_ids": ["uuid"]}} |
| 87 | +``` |
| 88 | + |
| 89 | +- Readback capped to 3 notes. |
| 90 | +- Returns raw note data (title, content, updated_at). LLM formats for speech via `finish`. |
| 91 | + |
| 92 | +### `delete_notes` |
| 93 | + |
| 94 | +```json |
| 95 | +{"name": "delete_notes", "arguments": {"note_ids": ["uuid"], "confirmation": "string"}} |
| 96 | +``` |
| 97 | + |
| 98 | +- LLM provides the `confirmation` prompt. Always confirmed before deleting. |
| 99 | + |
| 100 | +### `ask_followup` |
| 101 | + |
| 102 | +```json |
| 103 | +{"name": "ask_followup", "arguments": {"question": "string"}} |
| 104 | +``` |
| 105 | + |
| 106 | +- Used when the request is ambiguous. |
| 107 | + |
| 108 | +### `finish` |
| 109 | + |
| 110 | +```json |
| 111 | +{"name": "finish", "arguments": {"response": "string"}} |
| 112 | +``` |
| 113 | + |
| 114 | +- Spoken response to the user. Ends the loop. |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +## Context |
| 119 | + |
| 120 | +The first message in history contains: |
| 121 | + |
| 122 | +1. Current local time (captured once for caching) |
| 123 | +2. User request |
| 124 | +3. Minimal note index: id, title, updated_at for each note (sorted by recency) |
| 125 | + |
| 126 | +Subsequent turns append tool results and follow-up answers as history entries. The LLM resolves "my latest note" to the first id in the index. |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +## Safety Rules |
| 131 | + |
| 132 | +1. Python executes all note mutations, not the LLM. |
| 133 | +2. Overwrite requires confirmation (LLM writes the prompt). |
| 134 | +3. Delete requires confirmation (LLM writes the prompt). |
| 135 | +4. JSON saves safely overwrite by deleting any existing file before writing because `write_file()` appends to existing files. |
| 136 | +5. The loop is capped at 4 turns. |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## Validation |
| 141 | + |
| 142 | +``` |
| 143 | +python3 -m py_compile community/private-notes/main.py |
| 144 | +python3 validate_ability.py community/private-notes |
| 145 | +``` |
0 commit comments