01 / Mental model
The browser is the body. The API is the trust boundary.
The product is split so the browser can see the active Google Doc and use the microphone, while the server keeps long-lived OpenAI credentials out of the extension. Shared TypeScript contracts keep both halves speaking the same language. E-ARCH
Google Docs and HTML Docs credentials stay in the extension; OpenAI access stays behind the project API. The server is not a document proxy and never receives document-provider credentials. E-ARCH
02 / One idea’s journey
What actually happens when you press Start.
Session startup is a handshake across Chrome, Google, the project API, and OpenAI. The side panel owns the lifecycle and shuts itself down if you switch documents. E-APP
Identify the active supported document
The content script recognizes Google Docs URLs plus HTML Docs owner, long-share, and clean-share URLs. It sends provider, document ID, origin, and any share token to the background worker.
Acquire Google identity, then an app token
Chrome Identity yields a Google token. The API verifies the Google user and issues a one-hour app JWT subject to email/domain policy.
Select a provider client and install the protected scaffold
Google Docs creates session/category named ranges. HTML Docs places session/category markers inside a stable editable region. Both satisfy the same document-client contract.
Mint a short-lived Realtime credential
The extension requests a client secret from the project API, then establishes the voice connection directly to OpenAI over WebRTC.
Inject document context without speaking it aloud
The current snapshot is sent as silent context. During a live session, the app checks the revision every five seconds and reinjects context when it changes.
Listen, respond, and call tools
The Realtime agent can reread, search, append a protected note, prepare a proposal, or ask the API for deeper reasoning. Each tool call is timed and audited.
End cleanly and retain a minimal session record
Microphone and voice resources close; the recent conversation is summarized; transcript, proposals, metrics, and tool audit are sent to the session endpoint. The UI also offers deletion.
The active document is polled about every 1.5 seconds. A document change ends the running session instead of allowing the agent to keep writing against a stale document identity. E-APP
03 / Chrome extension
Five layers live inside the side panel.
The extension is a Manifest V3 React app, but its responsibilities are deliberately split between Chrome shell code, orchestration state, a provider-neutral document contract, provider implementations, and voice transport. E-MANIFEST E-PROVIDER
Chrome shell
manifest.json, background.ts, content.ts, and messages.ts register the panel, watch navigation, recognize both providers, and bridge active-document state.
Session orchestration
App.tsx selects the provider client and owns status, snapshots, credentials UI, session ID, transcript, proposals, microphone state, polling, errors, and cleanup.
Voice partner
voice.ts configures WebRTC, transcription, voice activity detection, model instructions, tool schemas, context injection, and rollover.
Document contract
document-client.ts defines one interface for read, protected-section setup, note append, proposal creation, and approved apply.
Provider safety clients
google-docs.ts uses named ranges and revisions; html-docs.ts uses stable regions, session markers, exact anchors, and pre-patch rereads.
Extension permissions
Chrome permissions are identity, sidePanel,
storage, and tabs. OAuth requests
openid, email, and full Google Documents access.
Host permissions cover Google Docs, HTML Docs (including local port 3000),
Google OIDC, OpenAI, and the local or deployed Rubber Duck API.
E-MANIFEST
The full Google Documents scope is powerful because the Google provider must read and edit the active doc. HTML Docs uses a per-document share token or an optional account API key stored in Chrome. A real rollout needs a clear privacy story, OAuth verification work, and tight allowlisting.
04 / Voice + tools
Conversation is realtime. Actions are explicit tools.
The voice partner runs on gpt-realtime-2.1 with the
marin voice, semantic VAD, near-field noise reduction, audio
output, and gpt-4o-mini-transcribe. Its system instructions frame
it as a concise senior partner and forbid claiming a write succeeded unless a
tool reports success. E-VOICE
E-OPENAI
| Tool | Purpose | Effect boundary | Safety posture |
|---|---|---|---|
| read_document | Refresh the document snapshot when the agent needs ground truth. | Active-provider read | READ ONLY |
| append_session_note | Add a normalized bullet to Ideas, Decisions, Open Questions, or Next Steps. | Protected named range | AUTO-WRITE |
| propose_document_edit | Create a before/after replacement for the user to inspect in the side panel. | Local proposal only | APPROVAL |
| deep_think | Escalate a difficult question to a stronger reasoning model and return structured insights. | Authenticated API call | NO DOC WRITE |
| search_context | Rank current-document paragraphs against query terms. | In-memory snapshot | READ ONLY |
The Realtime model keeps conversation fluid and can use tools. The deeper model—gpt-5.6-sol at medium reasoning—is called selectively for harder synthesis and returns strict JSON: answer, insights, and suggestedNotes. E-OPENAI
05 / Document safety
Named ranges define where autonomy stops.
Rubber Duck has two write lanes across both providers. One is narrow and automatic; the other is broad and gated. The provider-neutral contract preserves that invariant while each host gets its own conflict logic. E-PROVIDER E-DOCS E-HTML-DOCS
Protected Brainstorm section
- Google: root and category named ranges
- HTML Docs: a session marker inside one stable region
- Ideas
- Decisions
- Open Questions
- Next Steps
A note is whitespace-normalized and deduplicated. Google inserts a bullet at the named-range boundary; HTML Docs appends escaped HTML inside the matching category span.
The rest of the document
- Agent creates an anchored before/after proposal
- Side panel renders Apply and Dismiss
- Apply refetches the latest provider state
- Anchor must still occur exactly once
- Google maps offsets to Docs locations
- HTML Docs maps visible text inside one region
Ambiguity, missing anchors, unsupported modes, or unresolved revision conflicts fail closed.
How a safe replacement works
Proposal is inert
Creating it does not write to either provider. It exists in extension state as before/after text plus an anchor.
User chooses Apply
The client refetches the newest document snapshot rather than trusting the version that produced the proposal.
Anchor is revalidated
The original text must occur exactly once. A missing or repeated anchor is unsafe, so no mutation is attempted.
The provider maps visible text safely
Google converts plain-text boundaries into Docs structural locations. HTML Docs resolves visible text inside exactly one editable region while preserving surrounding markup.
Provider-specific guarded update
Google uses a revision-aware batch update and preserves the final structural newline. HTML Docs rereads the exact region immediately before PATCH and retries once if it changed.
Office compatibility mode is rejected by the Google provider because its structure cannot satisfy the same invariants. HTML Docs additionally refuses documents with no editable text region or missing session/category markers. E-DOCS E-HTML-DOCS
06 / Context lifecycle
The document remains the source of truth.
Context is not a one-time prompt dump. The extension maintains a revision-aware snapshot and injects changes silently so the conversation can respond to edits made by the human. E-APP E-VOICE
Large document context is capped at roughly 24,000 characters by keeping the
first and last 12,000 characters. Search currently lowercases query terms,
scores term containment across current-document paragraphs, and returns the
best matches. A ContextProvider interface leaves room for broader
sources later. E-VOICE
E-EXT-NET
Realtime credentials and long-lived voice sessions are treated as renewable. Rubber Duck carries forward a compact summary instead of assuming one connection can live forever. E-VOICE
07 / Backend + auth
The server proves identity and protects expensive capabilities.
The Fastify API verifies Google identity, enforces the team allowlist, signs one-hour app JWTs, holds the OpenAI key, mints ephemeral Realtime credentials, rate-limits deeper reasoning, and stores end-of-session records. E-API E-API-AUTH
| Route | Role | Guard |
|---|---|---|
| GET /health | Liveness response for local or deployed checks. | Public |
| POST /v1/auth/exchange | Verify Google userinfo, enforce allowlist, return app JWT. | Google bearer token |
| POST /v1/realtime/session | Mint a short-lived OpenAI Realtime client secret. | App JWT |
| POST /v1/deep-think | Run structured synthesis with the stronger reasoning model. | App JWT + 12/min limit |
| POST /v1/sessions/:id/end | Store the completed session payload with ownership and expiry. | App JWT |
| DELETE /v1/sessions/:id | Delete only the caller’s own stored session. | App JWT + owner match |
Identity chain
- Chrome obtains a Google access token.
- API calls Google OIDC userinfo.
- Email or domain is checked against policy.
- API signs a one-hour HS256 app JWT.
Abuse controls
- Global limit: 120 requests/minute
- Deep think: 12 requests/minute
- Strict CORS origin in production
- Schema validation on request bodies
- Owner-scoped session deletion
The extension keeps the user’s Google token plus any HTML Docs share token/account key client-side. The backend alone holds the long-lived OpenAI API key. The browser receives only a short-lived Realtime client credential. E-API-AUTH E-HTML-DOCS E-OPENAI
08 / Data + retention
Shared contracts make the session inspectable.
The shared workspace defines the domain shapes used across browser and server: session states, document snapshots, transcript turns, proposals, categories, tool audits, and persisted session payloads. E-SHARED
Core domain types
- SessionStatusUI lifecycle
- DocumentSnapshottext + revision
- TranscriptTurnspeaker + content
- EditProposalbefore / after
- ToolAuditEntrylatency + result
- SessionRecordretained payload
What is saved at session end
The app submits the session identity, document identity, transcript, proposals, summary, timing metrics, and tool audit. The store adds ownership and expiration. A database URL selects PostgreSQL; otherwise the API uses an in-memory store.
Expired sessions are purged every six hours. The default retention window is 30 days, and the DELETE route lets the authenticated owner remove a record earlier. E-STORE
Privacy boundary in plain English
Live document content is read in the extension and supplied to the voice model as context. Deep-think calls send the relevant question and context to the project API, which calls OpenAI. Session-end records can include transcript and tool-audit data. Google and HTML Docs credentials do not become stored session data, and raw workspace source files are not part of this guide’s published artifact.
The exact deployment policy still matters: production should set the allowlist, CORS origin, secrets, database, and retention values intentionally. E-README E-API
09 / Run + deploy
Local development has two moving parts.
The API and extension run separately. The extension expects the local API unless its build-time API base URL points at a deployed service. E-README
# terminal 1 — backend npm run dev:api # terminal 2 — extension build/watch npm run dev:extension # project verification npm test npm run typecheck npm run build
Local request path
Chrome loads the unpacked extension. The side panel talks to the API on localhost, while Google Docs or HTML Docs reads/writes and the Realtime WebRTC connection originate from the browser. If the API is down, authentication exchange and Realtime startup cannot complete; the extension surfaces a targeted offline diagnostic. E-EXT-NET
HTML Docs setup
Owner pages use an account API key saved in chrome.storage.local.
Share URLs supply a narrower document token automatically. When both exist,
reads prefer the document token; a write rejected as read-only retries with
the saved account key. The credential is sent only to the active HTML Docs
origin and never to the Rubber Duck backend.
E-HTML-DOCS
E-APP
Cloud Run shape
The provided service manifest deploys the API with scale-to-zero, a maximum of 10 instances, concurrency 40, and a 30-second request timeout. OpenAI, JWT, and database values are injected as secrets; CORS and allowed-domain policy are environment configuration. E-DEPLOY
After deployment, rebuild the extension with VITE_API_BASE_URL aimed at the Cloud Run API and make sure the extension’s host permissions and production CORS origin agree.
Current verification footprint
The current suite contains 24 cases across API and extension workspaces. At snapshot time, 23 pass and one HTML Docs credential-fallback test fails: retries a read-only share-link write with a saved account key. Coverage also includes health/auth routes, Realtime credentials, the deep-think contract, storage ownership/expiry, both providers’ URL parsing and write edges, context ranking, and offline API recovery. E-TESTS
10 / Failure map
Most failures have a clear boundary.
The fastest debugging move is to identify which handshake failed: active document, Google auth, project API, Realtime transport, Docs mutation, or extension asset loading.
The content script did not resolve a Google Docs or HTML Docs document ID, or the active tab is no longer a supported document route. Check the URL and reload the extension after rebuilding.
The extension cannot reach the configured Rubber Duck API. Start npm run dev:api locally or verify the deployed base URL and CORS configuration.
A rebuilt extension can leave an already-open side panel pointing at an old hashed asset. The voice code is now loaded eagerly with stable chunk names, but Chrome still needs an extension reload after a new build.
Google identity still authenticates the Rubber Duck API even during an HTML Docs session. Check OAuth client configuration, scopes, Chrome identity state, and the email/domain allowlist.
An owner URL needs a saved account key. A share token may be read-only; when an account key is also saved, the client retries a denied write using that key.
The anchor may have changed, disappeared, or become ambiguous since the proposal was created. This is a safety stop; recreate the proposal against the latest document.
Convert the file to a native Google Doc. Rubber Duck intentionally declines writes where its index and named-range invariants are not reliable.
This is deliberate. A live session is bound to one document ID and stops rather than carrying its context and write authority into another doc.
Inspect the tool audit and provider error. Conversation transport and document mutation are separate paths, so one can succeed while the other is rejected.
The mutation behaviors above are grounded in both clients’ explicit retries and fail-closed checks. The dynamic-import diagnosis reflects the extension’s bundled-asset lifecycle and its current eager voice import/stable chunk configuration. E-APP E-DOCS E-HTML-DOCS
11 / Honest limits
It feels collaborative, but it does not yet have document-native presence.
The current architecture is an agent operating through provider APIs plus a Chrome side panel. That gives it document awareness and controlled edit authority, but neither client implements cursor, selection, or presence state. Google’s REST API also does not expose the native coauthor-presence protocol. E-ARCH
What it cannot show today
- A native named collaborator cursor in either provider
- Live selection highlights owned by the agent
- Google Docs presence/avatar state
- HTML Docs agent-presence state
- True simultaneous character-by-character coauthoring
What is feasible in this architecture
- Voice and transcript presence in the side panel
- Tool activity such as “reading,” “thinking,” and “drafting”
- Before/after previews with explicit approval
- Controlled auto-notes inside the protected section
Google’s document API exposes content and batch edits, not the internal realtime collaboration surface used for human cursors and selections. A visual “Duck cursor” there could only be simulated as an extension overlay. HTML Docs is a more plausible home for first-class agent presence because its surface and API can evolve together, but the current region API still exposes content edits, not live cursor/selection events. The project deliberately avoids inferring either provider’s selection state from rendered DOM.
Other present boundaries
- Context search covers the current document only; external workspace sources are not connected.
- Google support is limited to native Google Docs; HTML Docs owner and share routes are separate supported surfaces.
- There is no Google Meet integration.
- The in-memory session store is a development fallback, not durable production storage.
- The product remains a private-team pilot and needs OAuth, policy, observability, and accessibility hardening before broad release.
Rubber Duck can act like a thoughtful second person through voice, memory, visible tool state, and approval-aware edits. Today that person lives in the side panel. A truly native Duck cursor requires a presence API in an owned document surface—not just REST writes.
12 / Source ledger
Every architectural claim has a trail.
This guide was reconciled against a private local workspace snapshot taken at 2026-07-26 02:34:12 UTC. The workspace has no Git HEAD, so file fingerprints—not a commit—freeze the reviewed evidence. Credential files, dependencies, generated output, VCS internals, the lockfile, and minified assets were excluded.
Evidence families and SHA-256 fingerprints
- E-README
README.md · lines 1–167Product purpose, two-provider setup, privacy, and pilot limitations.sha256 750205ddcd9c2e7c9cb8df11e3c95b9e7042e13c414ccaf65dd79cb117cdb186 - E-ARCH
docs/architecture.md · lines 1–46System boundaries, provider trust, document invariants, and context lifecycle.sha256 9dd1867f9b7368b13b393ebb5e3d39026cd654f971b08ef2dc4c55b31e6de40f - E-MANIFEST
apps/extension/public/manifest.json · lines 1–64Chrome permissions, OAuth scopes, provider hosts, side panel, and content scripts.sha256 64b81b9def6c99454c278a6a3397c24c5a652f9e7dfa314adec99111164374e1 - E-EXT-SHELL
background.ts · content.ts · messages.tsProvider-aware active-document detection, side-panel enablement, and messages.sha256 d5fbe582…970637 · 570ceaff…d55a · 0adf948d…34d0d - E-APP
apps/extension/src/App.tsx · lines 1–687Provider selection, HTML Docs credential UI, lifecycle, proposals, polling, and microphone cleanup.sha256 10d511f113e757fa030d883a92abd6e6d67ce54dd9b03e591e4ddc8916e297d5 - E-PROVIDER
apps/extension/src/lib/document-client.ts · lines 1–32Provider-neutral read, protected-section, note, proposal, and apply contract.sha256 a1ba889c1ed54308c3cf2e18b86ae89f985b7709ec9b63b700a55d4ed18af2c4 - E-VOICE
apps/extension/src/lib/voice.ts · lines 1–438Provider-neutral Realtime configuration, tool contracts, context, rollover, and audit.sha256 57409b734537961883f4c884df0c1fd635f7391513fc916e38073525f34dc087 - E-DOCS
apps/extension/src/lib/google-docs.ts · lines 1–433Google Docs parsing, named ranges, revision writes, proposal conflicts, and final-newline handling.sha256 39e589cee5a4738f3d78cf842fb5be7aa4748e60dbea2235718dc4fd69ccd129 - E-HTML-DOCS
html-docs.ts · html-docs-auth.tsHTML Docs shell/regions, share and account credentials, session markers, exact anchors, and pre-PATCH conflict checks.sha256 f1b38f96…3b1679 · dc620917…5b103 - E-EXT-NET
apps/extension/src/lib/auth.ts · api.ts · context.tsToken exchange, backend calls, offline diagnostics, and current-document search.sha256 b5b36ec2…7792b · 53331dff…d03ac · beeab864…42a26c - E-API
apps/api/src/app.ts · lines 1–173Fastify routes, validation, rate limits, CORS, and retention endpoints.sha256 d0de3a5893382fc250cf7722f58e6e4212dacac7c9b8d07cf4e7f2b2ccf2b809 - E-API-AUTH
apps/api/src/auth.ts · lines 1–79Google userinfo verification, allowlisting, and one-hour app JWTs.sha256 b4c26e23ed77fe279013daec44f7340be45854a7152b4bfbd69b1fb126c85282 - E-OPENAI
apps/api/src/openai.ts · lines 1–89Ephemeral Realtime credentials and the deeper reasoning contract.sha256 344ed9356d59ad7aec25c5be3ff929320c94cc52055f02863ef6d855f4b74072 - E-STORE
apps/api/src/store.ts · apps/api/src/server.tsMemory/Postgres storage, ownership deletion, expiry purge, and server lifecycle.sha256 47affec8…6a13 · 9be7b7c4…767e41 - E-DEPLOY
deploy/cloudrun-service.yaml · lines 1–44Cloud Run topology, secret injection, autoscaling, concurrency, and timeout.sha256 fb7111f1085a365ca35e2465eb3c19dcf01290bd329552137cfa5a025a048457 - E-TESTS
apps/**/*.test.ts · 24 cases23 passing and 1 HTML Docs credential-fallback test failing at snapshot time; covers API auth/storage, both provider clients, context, URL parsing, and offline recovery.aggregate fingerprint not frozen; individual source files remain local
A machine-readable version of this ledger is stored beside this document as
docs/rubber-duck-project.sources.json. Only summarized evidence is
embedded here; private source code and credentials are not.