Skip to content

Improve private notes voice flows#255

Closed
Ju-usc wants to merge 7 commits intoopenhome-dev:devfrom
Ju-usc:feat/private-notes
Closed

Improve private notes voice flows#255
Ju-usc wants to merge 7 commits intoopenhome-dev:devfrom
Ju-usc:feat/private-notes

Conversation

@Ju-usc
Copy link
Copy Markdown
Contributor

@Ju-usc Ju-usc commented Apr 24, 2026

What does this Ability do?

Improves the existing community/private-notes voice-first note ability. It adds topic search, safer note mutation handling, Python-owned destructive confirmations, and clearer voice UX expectations.

Suggested Trigger Words

  • private note
  • private notes
  • take a note
  • note this down
  • read my notes
  • delete my notes

Type

  • New community Ability
  • Improvement to existing Ability
  • Bug fix
  • Documentation update

External APIs

  • No external APIs
  • Uses external API(s):

Testing

  • Tested in OpenHome Live Editor
  • All exit paths tested (said "stop", "exit", etc.)
  • Error scenarios tested (API down, bad input, etc.)

Additional validation:

  • python3 -m py_compile community/private-notes/main.py
  • python3 validate_ability.py community/private-notes
  • git diff --check -- community/private-notes/main.py community/private-notes/README.md community/private-notes/SPEC.md
  • Live OpenHome CLI chat tested save, topic recall/search, delete confirmation, and post-delete no-match behavior against a deployed test copy.

Checklist

  • Files are in community/private-notes/
  • main.py follows SDK pattern (extends MatchingCapability, has register_capability + call)
  • README.md included with description, suggested triggers, and setup
  • resume_normal_flow() called on every exit path
  • No print() — using editor_logging_handler
  • No hardcoded API keys — using placeholders
  • No blocked imports (redis, user_config)
  • No asyncio.sleep() or asyncio.create_task() — using session_tasks
  • Error handling on all external calls
  • Tested in OpenHome Live Editor

Anything else?

Runtime notes:

  • GitHub says this branch cannot automatically merge yet, but still allows opening the PR.
  • openhome-cli assign --json reported success while status still showed empty personality_ids, so the live chat transcript was treated as the stronger attachment proof.
  • Temporary deployed test abilities could not be deleted because the CLI/backend returned Delete endpoint not yet implemented.

Copilot AI review requested due to automatic review settings April 24, 2026 01:32
@Ju-usc Ju-usc requested a review from a team as a code owner April 24, 2026 01:32
@github-actions
Copy link
Copy Markdown
Contributor

✅ Community PR Path Check — Passed

All changed files are inside the community/ folder. Looks good!

@github-actions
Copy link
Copy Markdown
Contributor

🔀 Branch Merge Check

PR direction: feat/private-notesdev

Passedfeat/private-notesdev is a valid merge direction

@github-actions
Copy link
Copy Markdown
Contributor

✅ Ability Validation Passed

📋 Validating: community/private-notes
  ✅ All checks passed!

@github-actions github-actions Bot added the community-ability Community-contributed ability label Apr 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Lint Results

__init__.py — Empty as expected

Files linted: community/private-notes/main.py

✅ Flake8 — Passed

✅ All checks passed!

@Ju-usc Ju-usc changed the title [codex] Improve private notes voice flows Improve private notes voice flows Apr 24, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an improved community/private-notes voice-first notes ability for OpenHome, using a constrained “tool loop” design where the LLM selects an action and Python executes it (including confirmations for destructive actions).

Changes:

  • Added a new PrivateNotesCapability implementing write/read/search/delete note flows with Python-owned confirmations and safer storage normalization.
  • Implemented JSON file persistence (private_notes.json) with strict validation and safe overwrite behavior (delete then write).
  • Added ability documentation (README.md) and a more detailed design/spec (SPEC.md).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
community/private-notes/main.py Implements the private notes tool-loop, notebook persistence/validation, and confirmation-gated mutations.
community/private-notes/README.md Documents user-facing behavior, storage model, and trigger phrases.
community/private-notes/SPEC.md Captures architecture, data model, tool contracts, and safety rules for the ability.
community/private-notes/init.py Adds the package init file required by ability validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +153 to +165
if tool_name == "ask_followup":
question = tool_args.get("question", "")
if self._looks_like_destructive_confirmation(question):
history.append({
"role": "user",
"content": (
"Do not ask confirmation with ask_followup. "
"Python handles delete and overwrite confirmation. "
"Call delete_notes or write_note with the matching note ids now."
),
})
continue

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_looks_like_destructive_confirmation() flags any ask_followup question containing words like "update"/"replace" as a destructive confirmation. This will incorrectly suppress legitimate clarification questions (e.g., “What would you like to update in that note?”), pushing the model into mutation tools prematurely and breaking update flows. Tighten detection to confirmation-style phrasing (e.g., yes/no questions like “Are you sure…”, “Do you want me to delete/overwrite…”) rather than keyword-matching action verbs.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +114
# Capture time once so the context prefix stays identical across turns (LLM caching).
now = datetime.now(ZoneInfo(self.capability_worker.get_timezone()))

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now is captured once at ability start and then reused for all tool executions, which means note created_at/updated_at timestamps reflect the initial trigger time even if the user spends time in follow-ups / confirmations. Consider keeping a stable context_now for prompt caching, but generating a fresh datetime.now(...) (or ISO timestamp) at the moment a mutation executes.

Copilot uses AI. Check for mistakes.
Comment on lines +203 to +217
notes = sorted(
notebook["notes"], key=lambda n: n.get("updated_at", ""), reverse=True
)
note_index = {
"note_count": len(notes),
"notes": [
{"id": n.get("id"), "title": n.get("title"), "updated_at": n.get("updated_at")}
for n in notes
],
}
return (
f"Current local time: {now.isoformat()}\n"
f"User request: {request_text}\n"
f"Note index:\n{json.dumps(note_index, ensure_ascii=True)}"
)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial LLM context includes the full note index for every stored note (id/title/updated_at). As the notebook grows, this will increase prompt size/latency and can eventually exceed model context limits, causing failures or degraded behavior. Consider capping the index (e.g., most recent N notes) and using search_notes/ask_followup to reach older notes when needed.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +114
# Capture time once so the context prefix stays identical across turns (LLM caching).
now = datetime.now(ZoneInfo(self.capability_worker.get_timezone()))

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ZoneInfo(self.capability_worker.get_timezone()) can raise if get_timezone() returns None or an invalid tz name (common in other abilities, which fall back to a default). Consider tz_name = self.capability_worker.get_timezone() or "UTC" and catching ZoneInfoNotFoundError to fall back safely, so the ability doesn't fail on missing/unknown timezone data.

Copilot uses AI. Check for mistakes.
@Ju-usc
Copy link
Copy Markdown
Contributor Author

Ju-usc commented Apr 24, 2026

Closing this conflicted branch in favor of the clean replacement PR: #256

@Ju-usc Ju-usc closed this Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-ability Community-contributed ability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants