fix(email): make Outlook/Gmail mail show up in the email app - #4
Merged
Conversation
…time - Fix config.json fields to match what the system actually consumes (add tool_scope, icon, category, webhook_routes; mark model_tier/execution_budget/authority as not consumed) - Add §2a: Platform-Injected Tools catalog (15+ tools agents get for free) - Add §2b: tool_scope documentation (Berkeley leaderboard accuracy fix) - Fix naming convention: bare slug in config.json, no agent- prefix - Add §18: Agent Registration Flow (step-by-step walkthrough) - Add Key files table explaining system.md vs instructions.md vs .agent.md - Expand §17: mutation sandbox Docker process, auto-push gate, human review queue - Update build checklist to 22 items, add 3 anti-patterns - Clarify github-copilot vs maf runtime modes and how agent_runtime is auto-set
Five bugs, all confirmed against production data, prevented connected mailboxes from displaying any mail: 1. Doubled API path: the email app's gatewayFetch prefixed `/api/email` while callers also passed `/email/...`, so every request hit `/api/email/email/...` and 404'd. Prefix with `/api` instead. 2. Folder mismatch: providers persisted opaque provider folder IDs (Outlook parentFolderId, Gmail labelIds[0]) while the UI/gateway query `WHERE folder = 'inbox'`. Added canonical_folder() and normalize on sync; gateway folder filter is now case-insensitive + handles starred. 3. Background sync never started: scheduler used structlog-style kwargs on a stdlib logger, raising TypeError in start_background_sync(). Converted to %s formatting. 4. Token refresh impossible: the OAuth callback stored tokens without the client_id/secret/tenant needed to refresh. Persist app creds with the token blob, and write back rotated tokens after sync/folder calls. 5. Gmail received_at always None (datetime.timezone on the datetime class). Adds tests/unit/test_email_folders.py for the folder normalization. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vjvarada
added a commit
that referenced
this pull request
Jul 3, 2026
Ships observability Phases 1+2 (module map review #4, specs/observability_e2.md) so "error X happened with agent Y" is debuggable after the fact. E2 C+ -> B+. The gap: logs were uncorrelated colored-console text (couldn't grep by run/agent) and the full run trace lived only 1h in Redis (latest-run-only), with the exception message but NO traceback anywhere durable. Phase 1 - correlated, JSON-able logs (packages/acb_common/_log.py): - bind_run_context / clear_run_context / get_run_context bind run_id/thread_id/agent/user into structlog contextvars. The executor binds them at the run_agent_stream boundary (clears in finally), so EVERY log line the run emits - incl. acb_llm.usage, which passes no ids - is auto-correlated via the merge_contextvars processor. - LOG_FORMAT=json (or json_logs=) switches ConsoleRenderer -> JSONRenderer; prod enables it via the systemd EnvironmentFile, no code change. - LLM usage audit rows now carry the run context + an agent:<name> actor. Phase 2 - durable per-run trace store: - Migration 50 adds the agent_run table (metadata, status, tokens, duration, tool_summary, error_message/type/traceback, trace JSONB, flagged), indexed for the diagnostics queries (by agent, status, thread, time; partial on status='error'). - gateway/run_trace.py: build_run_trace_row (pure, unit-tested) derives status from the events + a tool summary; record_run_trace upserts it, wired into chat_fold.persist_final_assistant_message (reuses the run-end Redis replay). Retention: metadata + tool summary for ALL runs; the full trace + traceback ONLY for errored/cancelled/flagged runs (storage + privacy). - Traceback capture: the executor run-error handler logs exc_info=True and the agent_run_error audit payload now includes error_type + traceback. Debug workflow (SSH): psql agent_run WHERE agent_name=Y AND status='error'; journalctl -u acb-gateway -o cat | grep the agent (JSON mode) for the stack. Designed-not-built (in the spec): Phase 3 /debug/runs diagnostics API, Phase 4 one-command VPS feature-test harness (builds on tests/integration/ test_chat_features.py). Tests: tests/unit/test_observability.py (11). Migration validated idempotent against the live DB + an E2E write/read-back. Full suite 664 green, zero regressions. schema.generated.sql regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vjvarada
pushed a commit
that referenced
this pull request
Jul 12, 2026
…e (BO-1) The Action Broker — the one component allowed to write back to source systems (AGENTS.md non-negotiable #4) — was a 46-line stub whose propose() only wrote an audit row. Turn it into a real, tested, NON-BREAKING core (nothing imported it; it ships with zero handlers so it still cannot perform any real write): - decide_disposition(authority, destructive): the pure authority-tier policy. READ -> rejected; AUTONOMOUS -> auto-apply; SUGGEST -> needs-approval; SUGGEST_APPLY -> auto-apply for reversible actions, needs-approval for destructive/outward ones (FAIL CLOSED, per the harness rule). - propose() now computes + audits the disposition (destructive=True default so an un-annotated action fails closed). - Executor registry: register_action_handler / execute. A real provider write happens ONLY inside a registered handler; an action with no handler is REFUSED (never silently applied); rejected proposals never execute. All audited. - 8 unit tests (policy matrix + fail-closed executor). Still pending (needs per-agent authority decisions + a queue table, tracked in the checklist): a pending_actions table, approval-inbox binding, real handlers, and routing the existing ClickUp/email writes through execute(). Full unit suite: 887 passed; trajectory evals: 118 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EQmgixG7WncBMumvLNsMz8
vjvarada
added a commit
that referenced
this pull request
Jul 13, 2026
Exposes the pending_actions queue over HTTP so an operator (Control Plane
inbox) can review outward-write proposals — the sibling of the self-mutation
approval routes.
GET /actions/pending → the approval queue
POST /actions/pending/{id}/approve → execute the registered handler
POST /actions/pending/{id}/reject → refuse, never executed
Every endpoint is gated on require_internal_auth (payloads carry outward-write
bodies — never anonymous-reachable). approve/reject delegate to
action_broker.approve/reject, which fail closed. Registered in main.py behind
the same try/except guard as the other routers (a load error can't crash the
gateway). The broker still has zero handlers, so approve() returns "no handler"
until real handlers are wired — the queue is visible + auditable but inert
(non-negotiable #4 intact).
Tests (tests/unit/test_actions_routes.py, 4): handlers delegate to the broker,
reviewer identity threads through (with an "operator" fallback), and every
write route carries require_internal_auth. Verified the full app imports with
all three routes registered.
Remaining under A2: the Next.js proxy + inbox pane, real handler registration,
rerouting the bypassing ClickUp/email writes through submit() (needs B1), and
live-Postgres integration verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vjvarada
added a commit
that referenced
this pull request
Jul 13, 2026
Makes the broker the single audited chokepoint for the ClickUp outward writes (create_task / update_task / create_project), per non-negotiable #4 — without changing default behaviour. These writes are already user-approved (staged sync_state='pending' → the user pushes), so a new BaseTaskProvider._broker_gate() AUDITS each write via action_broker.propose() and AUTO-APPLIES it (runs the real write immediately, result unchanged). Every outward task write now flows through — and is recorded by — the broker. - ACTION_BROKER_ENFORCE kill-switch (default off): flip an action (or "all") to the approval QUEUE instead — flippable via env + restart, no redeploy. The queue path returns a pending marker; executing on approval needs a persistent handler (documented follow-up), so it stays off by default. - Fail-safe: a broker-layer error never blocks a user-approved write (do_write still runs). do_write executes exactly once; its own HTTP errors propagate. - Audit payloads carry only non-secret write metadata (title/status/fields) — never the token (handlers use self._headers()). Tests (tests/unit/test_provider_broker_gate.py, 4): auto-apply default, queue when enforced, per-action enforce, and fail-safe on broker error. Full non-e2e subset green (833 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vjvarada
pushed a commit
that referenced
this pull request
Jul 24, 2026
…roker
A one-to-many broadcast ("send this monsoon update to all 18 dealer groups") is
the spec's canonical always-approval outward write. /whatsapp/broadcast resolves
the targets (explicit chats or a whole category, owner-scoped) and routes ONE
Action Broker proposal with SUGGEST authority — which the broker maps to
NEEDS_APPROVAL — so nothing sends until a human approves; the route asserts the
disposition was gated (500s if a future change ever made it auto-apply). The
real sends live only in the registered whatsapp.broadcast handler (broker
non-negotiable #4), one failed recipient never aborts the rest. Handler is
registered at startup alongside the post-sync hooks. action-broker added as an
explicit gateway dep. 4 tests pin the never-auto-apply invariant, handler
registration, route, and request shape. Full WA suite 123 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UuXLzXKbBfbJDypE9V3Ffq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
User connected Microsoft Outlook but no mail appeared in the email app. Diagnosed against the live VPS — the mail was actually synced (200 messages in
email_messages) but three independent bugs sat between the database and the screen, plus two that would break things shortly after.Bugs fixed (all confirmed against production)
gatewayFetchprefixed/api/emailwhile every caller also passed/email/..., producing/api/email/email/accounts→ gateway/email/email/accounts→404. Gateway logs showed this repeating. Now prefixes/api.parentFolderId, GmaillabelIds[0]) while the UI/gateway queryWHERE folder='inbox'. Addedcanonical_folder()and normalize on sync; gateway folder filter is now case-insensitive and treatsstarredas a flag.start_background_sync()used structlog-style kwargs on a stdlib logger →TypeError: Logger._log() got an unexpected keyword argument 'accounts'(seen in journal asgateway.email_sync_skipped). Converted scheduler logging to%s.client_id/client_secret/tenant_id. Now persisted with the token blob, plus write-back of rotated tokens after sync/folder calls.received_atalways None (datetime.timezoneused on thedatetimeclass).Post-merge steps
acb-gateway, rebuilds workbench).anonymousduplicate account already removed from prod DB.Tests
tests/unit/test_email_folders.py(folder normalization) — passing.🤖 Generated with Claude Code