Skip to content

Phase 4: first value, approvals that escalate, skills, new website - #9

Merged
Taskmaster-1 merged 21 commits into
mainfrom
feat/phase4
Oct 28, 2026
Merged

Taskmaster-1 merged 21 commits into
mainfrom
feat/phase4

Conversation

@Taskmaster-1

Copy link
Copy Markdown
Owner

Phase 4: make what makes Synapse different visible, and get people to value fast. Stacked on #8 (Phase 3); merge that first.

First value

  • Writes in your voice: "Learn my writing style" reads recent sent mail (your own text only: quoted replies, signatures and received mail never reach the model), stores a ~70-word style profile as an editable "Writing style" memory, and drafts use it. The emails themselves are never stored.
  • Template gallery: 19 ready-made automations in 5 categories (daily brief, inbox triage, draft replies, meeting prep, follow-up nudges…). "Use" opens the create form prefilled; templates that need Google or an MCP connection say so.
  • Getting started on Home: connect Google → learn your style → turn on an automation → approve your first action, driven by real state.

Approvals that don't wait forever

  • One reminder (default 30 min), escalation to team admins (default 60 min, workspaces only), then expiry as an ordinary deny the agent sees (default 24 h). A sweeper in the worker loop; every step is a compare-and-set, so several workers act once.
  • Two-person sign-off (workspace setting) for irreversible actions, optionally sends: two different people, any deny ends it, the undo window starts after the second approval. An edit counts as the editor's approval only and sets aside earlier approvals, so both approvals always cover the input that runs.
  • Plain-language risk facts on cards and in push, Slack and email: "Sends to 1 person outside acme.com", "Can't be undone", "After reading outside content".

Skills, and a read never widens the write

  • Skills in the open Agent Skills format (SKILL.md): six built in, plus your own (Skills page, import, or saved from a chat with approval). The prompt lists one line per skill; load_skill loads the rest on demand. Every use is recorded with a content-hash version. Skills are instructions, never permissions.
  • New-target rule: in a tainted run, a send, delete or write to a recipient, site or destination that only appeared in outside content always asks ("New recipient attacker@evil.example appeared only after reading outside content"), even when a plan grant or learned rule would allow it. Block rules still win. New eval scenario.

Website

  • New hero: a phone push → Approve → 10-second undo → sent.
  • Try to hack it: edit a malicious email and watch the real policy engine (@synapse/core/policy, imported client-side) hold the send. Scripted model, labelled as a simulation.
  • Sections for what shipped in Phases 1–3, and a new /trust page (containment, permissions, where data lives, audit, honest limits, disclosure).

Notes

  • One migration, 0007_skills_and_approvals_that_expire.
  • The style-learning model call isn't counted toward run cost or daily budgets yet (it isn't a run).
  • In email-triggered runs, replying to the sender now shows the new-recipient reason (the address only appeared in the email). It asked before as well; only the reason is new.

Verified

  • 261 tests pass; lint, typecheck and web build pass.
  • Browser (puppeteer), all workstreams merged: landing, trust, home, inbox, automations, skills, memory, settings, activity at 1440 light and 390 dark; no console errors, no horizontal overflow.
  • Not tried against real Slack, phones, SMTP or a real Google account (fakes in tests).

🤖 Generated with Claude Code

…nboarding APIs

- POST /api/writing-style/learn reads ~25 sent emails (own text only: quoted replies and
  signatures dropped), asks the user's model for a short style profile and stores only that
  as an editable memory with source "style". The profile goes into the system prompt as a
  style block for drafting, apart from the other notes, and can't change rules.
- GET /api/automations/templates: 19 ready-made automations in one typed file, with what
  each still needs connected.
- GET /api/onboarding: real state for the Home checklist.
- Model resolution moved out of the worker so one-off calls share it.
riskFacts derives one to three short lines from a tool call, its effect and
taint ("Sends to 2 people outside acme.com", "Can't be undone", "After
reading outside content"), using the organisation's email domain when known.
Pure, exported as @synapse/core/risk for the web app.
…matters

- A sweeper in the worker loop sends one reminder (default 30 min), escalates
  team members' approvals to the team's admins (default 60 min, who may then
  decide them) and expires what nobody decided (default 24 h) as an ordinary
  deny the agent sees; the run continues. Each step is a compare-and-set on its
  own timestamp, so every worker can sweep at once.
- Two-person sign-off (team setting: destructive, or destructive and sends):
  the first approval is recorded and the call keeps waiting; a second, distinct
  person completes it and only then does the undo window start. Any deny ends
  it. An approval with edited input counts as the editor's only.
- Push, Slack and email carry risk facts, expiry, escalation and 1-of-2 state;
  Slack refreshes the original message and nudges in its thread.
- Activity and the audit CSV record both approvers, reminders, escalations
  and expiries.
Shows the learned profile apart from other notes, with a Learn button once Google is
connected (a Connect Google link before that). Only the description is kept.
Cards grouped by category with a filter; Use opens the normal create form prefilled, and
templates that need Google, GitHub or Linear say so and link to Connections.
Connect Google, learn your writing style (right from the card), turn on an automation,
approve your first action. Read from /api/onboarding instead of probing recent runs;
falls back to any connection when the server has no Google app.
Exports @synapse/core/policy so the browser can import the pure policy
functions without the runtime's model providers. The demo's scripted agent
plays a fooled model; every call it proposes goes through
evaluateComposedPolicy with taint set as the runtime sets it. A test pins the
demo's tool effects to the real Gmail tools.
- Hero: an approval arrives as a lock-screen push, is approved, waits out
  the 10 s undo window, then sends. Fixed-size, pauses off-screen, static
  under reduced motion. Self-host command and GitHub link as the CTAs; the
  star count is fetched on the server (ISR, hourly) and omitted on failure.
- Try to hack it: edit a malicious email and run a scripted, fooled agent;
  the real policy holds the send and blocks the delete, with reasons.
- New sections: asks less over time (learned rules, plan approval, undo),
  approve anywhere, automations, teams and budgets, questions to ask any
  agent. Copy, ticker, rules and stats updated to what ships.
- Shared marketing nav/footer with Trust links; OG images for /welcome.
/trust explains the effect, rules, taint, approval and undo pipeline, how
team and personal rules combine, where data lives (self-hosted, your
Postgres and keys, no telemetry), the activity log and CSV export, honest
limits, and how to report a vulnerability (SECURITY.md). With its own
metadata and OG image.
…write

- skills.ts: SKILL.md parsing and validation, content-hash versions, a sorted
  byte-stable prompt listing, and load_skill (a trusted read) for progressive
  disclosure. Skills are instructions only; nothing in one changes policy.
- targets.ts: in a tainted run, a send, destructive call, or write to a named
  destination whose recipient, site or destination never appeared in the
  user's messages, the system prompt or the history before the taint point
  asks with a reason that names it. Plan grants and allow rules cannot cover
  it; block rules still win and admin sign-off still applies.
- The runtime records the run's loaded skills (name + version) on every later
  tool call and tool.call event, for audit.
- Six built-in skills ship as plain Agent Skills folders under
  packages/tools/skills, imported as text so they bundle with the server.
- Users switch built-ins on or off and create, edit, import (SKILL.md),
  export and delete their own: /api/skills. Stored in a new skills table.
- Each run lists its enabled skills in the system prompt and gets load_skill;
  skill_save (a write that always asks) lets the agent save one on request.
- Loaded skills are recorded on the run and on later tool calls (versioned).
- Event-trigger messages are marked untrusted, so an address quoted from an
  email or webhook payload never counts as the user's own.
- Eval: injected-recipient.
…r own

A Skills entry in the sidebar. Built-ins and your own skills in one list with
their versions; open one to read its instructions and reference files, or
edit, delete and download it as a SKILL.md.
docs/skills.md covers the format, progressive disclosure, built-ins, your own
skills, versions and audit, safety and the API. README, architecture and
SECURITY describe the new-recipient rule where they describe taint.
…gress

Cards carry plain-language risk chips, when the approval expires, whether it
was escalated, and 1-of-2 sign-off state; the first approver sees it waiting
for someone else (and can still deny). The team inbox lists escalated and
two-person calls; settings gain reminder/expiry (personal) and escalation,
team expiry and two-person sign-off (team). Activity names both approvers and
shows reminders, escalations and expiries.
The landing demo now judges calls with the new-target rule too.
…on sign-off

One migration for Phase 4 (skills and approval timing).
A paperclip in the chat box (plus drag and drop, and paste) uploads photos,
videos, PDFs or any file up to 25 MB. Files are stored privately; text files
and PDFs are also added to the knowledge base. The agent reads them with
file_read (untrusted, so reading taints the run) and can attach any of the
user's own files to gmail_send and gmail_create_draft, which then use Gmail's
multipart upload. Approval cards list the files that will go out, and risk
facts say how many.
@Taskmaster-1
Taskmaster-1 changed the base branch from feat/phase3 to main October 28, 2026 19:59
@Taskmaster-1
Taskmaster-1 merged commit 7701d43 into main Oct 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant