Short list of changes - - Automations quickstart - Slack mentions router - Live turns keep narration inside. Add commentary for tasks. - Approvals card UI enhancements - Newer models - Fable, Sol, Terra, etc added to list
49 KiB
Implementation Ledger — platform/personas branch
Running log for the whole platform/personas branch, across its workstreams (in order):
Personas/Permissions/Inbox (Phases 0–3, ✅ done) → Messaging refactor (✅ done) →
UI Refresh (in progress — see UI-REFRESH-KICKOFF.md).
Phase numbers are per-workstream. "Phase 0–3" below = the Personas effort (complete). The UI Refresh has its own Phases 1–5 (
UI-REFRESH-SPEC.md§9) — when logging UI-Refresh progress, write it as "UI-Refresh Phase N" to avoid colliding with the Personas phases.
Purpose. Single source of truth for where the implementation stands, so any session
(or person) can resume without re-deriving context. The design why lives in the per-effort docs
(PERSONAS.md, PERMISSIONS-AND-INBOX.md, UX-DECISIONS.md); this file tracks what's-done /
what's-next.
How to use (read this first when resuming):
- Check Current status below, then the first phase that isn't ✅.
- Do the next unchecked milestone; make its completion gate (the named tests) pass.
- Update the checkbox + the Session log at the bottom, and commit the ledger — that commit is the handoff to the next session.
Status legend: ⬜ not started · 🟡 in progress · ✅ done. Tests live in platform/tests/ (pytest).
Current status
- ALL PHASES (0–3) COMPLETE — backend, live wiring, and GUI. Local-only, Devika-authored.
Python: 444 passed (3 pre-existing SDK errors). GUI: full
npm run build(tsc + vite) passes. First-pass visual review DONE (Claude, in the running app): all four surfaces verified — persona-driven picker (Ops shows), Personas tab (toggles + install), Inbox view, Unattended toggle (one-tap confirm → amber on-state). Two correctness fixes applied + verified live: toggle reads its persisted state on mount (newGET /v1/sessions/{id}/unattended); removed the redundant Settings "Surfaces" toggles (now the Personas tab). - Phase 4 (UX/IA) — partly built. DONE + verified live: family-aware Projects, default-first,
Chat hidden, expand≠switch. DONE in code (not yet screenshot-verified — browser dropped): shared
top zone (Integrations/Automations above tabs), Pinned band + pin/unpin. Specs in
PERSONAS.md+PERMISSIONS-AND-INBOX.md. Remaining Phase 4 below. - Phase 5 (Messaging ↔ Sessions) DESIGNED, not built — channel subscription (pub/sub) vs.
Inbox (request/reply);
ask_usertool; durable correlation. Spec:MESSAGING-AND-SESSIONS.md.
🧭 RESUME HERE (next session, after compaction) — remaining-work plan
Worktree
/Users/rohit/fleet/ro4d/aisuite-personas, branchplatform/personas(local-only, Devika-authored, 0 behind origin/main). Test runner:./.runtests.sh. Servers may be running: backend127.0.0.1:8765, vitelocalhost:1420(restart backend after Python changes; vite hot-reloads). Sample persona for the install demo:~/coworker-personas/research-analyst.md.⚠️ Run the SERVER under the aisuite venv, not the agent-platform (test-runner) venv. The agent-platform venv lacks the
anthropic/google-genaiSDKs, so a server started under it errors every turn withModuleNotFoundError: No module named 'anthropic'(the default model isanthropic:claude-opus-4-8). Correct launch:PYTHONPATH="…/aisuite-personas/platform:…/aisuite-personas" …/aisuite/platform/.venv/bin/python -m coworker.server.run --port 8765. (Tests still use./.runtests.sh/ the agent-platform venv.)Ordered plan:
- ✅ Phase 4 (frontend) — attention/liveness badges, family-aware App gating, Search→top, Inbox→session link (answer-in-context B). Committed
d0b64d6. Dots + Inbox badges seen live by Rohit. Pending visual: the Inbox→session link + inline answer card — a real review session is left suspended for this (see hands-on testing below).- Phase 5 messaging (per
MESSAGING-AND-SESSIONS.md):
- ✅
ask_usertool (commits181260b,238b0c4) — engine-intercepted likerequest_directory. Mode-aware routing (the key contract): Unattended → KIND_QUESTION Inbox item + suspend oninbox.wait(manager.inbox_question_asker, also the default for background/self-wake runs); attended → a livequestion_requestedevent answered inline from the composer (question_response), NO Inbox item — the Inbox is for Unattended only. Question + optional quick-replyoptions+ free-text escape +multi(mirrors Claude Code's AskUserQuestion);InboxItemCardrenders option chips for both the inline live prompt and the Inbox. Tested e2e both ways (attended → live event, no Inbox; unattended → Inbox, no event): PASS. Not yet done: durable(session_id, tool_call_id)capture (still live-only).- ✅ Unified interactive prompts on parked Inbox items (commit
1e97e0f). All four prompts (approval / question / directory / plan) are parked items awaited viainbox.wait(the per-connection asyncio queues are gone), so they survive a dropped socket (redelivered on reconnect) and resolve from any surface.visibilitydecides where a prompt shows, not how it works: attended → inline only (the cross-session Inbox list filtersinlineout, so the Inbox = explicit Unattended only); unattended → cross-session Inbox + answer-in-context. Backend:InboxItemgainsvisibility+data+directory/plankinds; live WS responses resolve the session's single pending item, REST resolves by id; approver takes both vocabularies. Frontend:InboxItemCardrenders directory/plan; live cards suppressed when Unattended (App tracks it via ref + toggleonChange). Verified e2e (approval + ask_user, both modes). Boundaries (honest): (1) Layer 2 — a turn started by a socket that drops mid-stream still dies on the next send (its event stream is bound to that socket); re-attaching a live turn to a new socket = the deferred durable-resume / event-routing work. Server-triggered turns (self-wake, schedule, message) aren't socket-bound, so they park+resume fine. (2) live WS resolve uses "the session's one pending item" (safe: agent blocks on one at a time; a human can't out-race item creation). (3) directory grant from the Inbox/reconnect has no folder picker yet (uses the suggested path).- ✅ Channel subscription (inbound) (commit
66208e0).SubscriptionStore(persisted(session_id, channel), permanent until unsubscribe/delete); agent toolssubscribe_channel/unsubscribe_channel/list_subscriptions/get_channel_messages(ring buffer); agent bootstraps viaask_user, parses Slack<#id|name>. Gateway dispatch: token → Inbox; channel msg → buffer + fan out to subscribers viamanager.deliver_to_session(busy→steer/idle→turn, shared with self-wake); DM → super-agent. Loop prevention free (adapter drops bot-self). v1 filter = the subscription itself; mention/thread filtering deferred. Sessions are durable (never end except explicit delete) — documented inMESSAGING-AND-SESSIONS.md. Tested (store/parsing/buffer/tools/dispatch fan-out). Not yet: GUI to view/manage subscriptions;@botmention surfacing inMessageEvent; channel name→id picker; live Slack e2e (needs a connected workspace — logic is unit-tested).- ✅ Interactive Inbox prompts over Slack (commit
9d8798a). Mirrored items render as Block Kit cards with buttons (approve/deny, ask_user options); the item id rides in each button value, so a click resolves the exact item (no[ocw:id]-reply fragility). Socket-mode action callback →manager._on_interaction→inbox.resolve→ swaps buttons for the outcome. Free text isn't offered over messaging — open the app (token = legacy fallback). Provider-agnosticButton; v1 = single-select, Slack. Unit-tested. Not yet: Telegram inline keyboards, plan/directory buttons, live Slack click round-trip (needs Interactivity enabled on a connected workspace).- ✅ Hands-on testing (done 2026-06-27, via WS+REST against the running server, real model): (A) self-wake —
sleep_for→ schedulerresume_due_wakesresumed the session in ~16s: PASS. (B) Unattended — awrite_fileapproval parked in the Inbox (realattention=1, livenessworking), resolving it released the suspended agent and the file was written: PASS. Not yet tested: the free-textask_user/KIND_QUESTION path (doesn't exist until Phase 5 — only the KIND_APPROVAL path is wired). A review sessionhandson-unatt-6a3fed71is left suspended with a real pending approval so Rohit can see the badge + Inbox→session link + inline card in the GUI (resolve/dismiss it from the Inbox to clean up).
- ✅ Durable resume (commit
08f3bc4). A prompt (approval/question/directory/plan) pending when the process dies now continues its turn on answer, instead of orphaning. Inbox items are idempotent by(session_id, tool_call_id)(id persisted) + the thread is saved when a prompt suspends;engine.resume()re-drives ONLY the trailing assistant message's unanswered tool-calls — callbacks find the resolved item (no re-prompt), approvals re-execute via the normal authorize→execute path, answered calls are skipped (no double-exec).manager.resolve_inbox(REST + Slack button) durably resumes when the agent isn't live; Inbox-based default approver/directory/plan callbacks added so a rebuilt no-socket engine can resolve all four. Tested: question (inject answer) + approval (re-execute tool) survive a simulated restart. Boundaries: the legacy token-text-reply path (_resolve_inbox_reply, sync) does NOT trigger durable resume — buttons + REST do; the same-process socket-drop case is still the Layer-2 limitation (orphaned task completes server-side, events lost).- Messaging GUI — ✅ view-only (
4b0e828) + ✅ manage (<this>). Global "Channel subscriptions" table in Integrations (session → inbound channel + outbound routing + collision warning) with per-row unsubscribe + an add form (session select + ChannelPicker + Subscribe); per-session plug chip in the composer head opens a popover to add/remove this session's channels.ChannelPicker= datalist of recently-seen channels (GET /v1/channels/recent) + free typing (slack:C…or#mention). REST:POST /v1/subscriptions,/v1/subscriptions/ remove. Tested. Browser down on my end → Rohit verifies visually. (Recent list is empty until Slack messages arrive — typeslack:Ctestto exercise the UI.)- deepseek-chat picker bug — TABLED (Rohit, 2026-06-28). The quick filter patch was built + reverted; the real fix is a provider-model redesign (first-class OpenAI-compat providers) — see "Known bug" below. Current behavior kept (harmless cosmetic leak).
Design items parked (not now): per-coworker integrations (Connect-global vs Grant-per-persona); white-label build tooling; wake budgets/runaway detection.
- Marketplace (decided with Rohit 2026-06-27): NOT a hosted service. Personas load from a local dir or a GitHub repo and are snapshotted into our managed area at install (done). Built-in Code/Ops stay bundled/immutable. Updating a persona can change live sessions' prompt/ tools — accepted, noted, deferred. (White-label build tooling: deferred.)
- Live wiring progress:
- ✅ Approver-swap — the
/ws/sessionapprover routes to the Inbox + suspends when the session is Unattended (reuses the tested inbox machinery; resolved via/v1/inbox/.../resolve). - ✅ Scheduler/engine resume — no engine surgery needed (Rohit's framing): a self-wake
tool records a wake + the agent ends its turn; the scheduler tick (
extra_tick) callsresume_due_wakes(), which delivers the wake message busy →queue_steeringinto the live turn; idle → run a fresh background turn (the super-agent's proven pattern). Per-session in-flight flag set by/ws/session. Self-wake tools registered for knowledge personas. - ✅ Gateway inbound —
Gateway.reply_resolverconsumes inbound messages carrying an[ocw:<id>]token (resolve the item + release the suspended agent) instead of routing them as a new turn; the unattended approver mirrors the approval out to the bound Slack/Telegram channel with the token embedded. Bidirectional loop closed. - ✅ GUI pass (build-verified; visual review pending): Personas tab in Manage modal
(enable/surface/set-default + install from dir/GitHub with consent summary); the new-session
picker is now driven by surfaced personas (Ops appears), static set as fallback; an
Inbox view (approve/answer/dismiss, polled) wired as a sidebar surface; a per-session
Unattended toggle in the composer head (one-tap confirm).
api.tsextended. Fullnpm run buildpasses. Deferred polish for review: composer-disable while unattended; inbox routing config UI; reactive persona refresh after enable; inbox unread badge.
- ✅ Approver-swap — the
- Base: branch
platform/personasofforigin/main(local-only, Devika-authored).
Dev env (how to run tests): no venv in this worktree — reuse the agent-platform venv
interpreter with this worktree on PYTHONPATH (helper: ./.runtests.sh <pytest args>, which
runs …/agent-platform/platform/.venv/bin/python -m pytest with
PYTHONPATH=<root>/platform:<root>). That venv lacks the anthropic/google-genai SDKs and
black, so 3 provider tests error on import (pre-existing, unrelated) and formatting is by
hand. Everything else runs green.
Phase 0 — Foundation: tool catalog + risk classes
Goal: a declarative id → capability catalog + risk-class-driven permissions, with no
behavior change for existing agents. Both personas and permissions sit on this.
- ✅
risk.py—RiskClass(read/write_local/exec/external) +classify()with a user-local override seam;permissions.pyreads it instead ofWRITE_TOOLS/SHELL_TOOL/requires_approval(those re-exported for back-compat). - ✅
catalog.py— capabilities (code_files,files,git,search,shell,todo) withrequires+risk;expand(ids, ctx)skips capabilities whose context is absent. - ✅
agents/code.py+agents/cowork.pybuild viaexpand(...)(hand-written factories gone). - ⬜
never_unattended_autofloor flag — deferred to Phase 2 (where it's consumed).
Completion gate (tests): ✅ 41 passed.
test_catalog.py— expand reproduces the Code & Cowork toolsets exactly; requirement skipping; the single-root vs multi-root file-capability distinction preserved.test_permissions_risk.py—classifymapping (incl.external+ override precedence) and engine decisions across all 5 modes.- Existing
test_tools_permissions.py+ full suite green (348 passed; 3 pre-existing SDK-import errors unrelated).
Note: read_file_lines stays Cowork-only (Code folds it into the windowed reader) — the
two file capabilities (code_files single-root numbered vs files multi-root) preserve each
surface's exact toolset.
Phase 1 — Personas core
Goal: personas as data; Code/Cowork become manifests; a session is born from exactly one persona; lifecycle toggles in settings.
- ✅
personas/manifest.py—PersonaManifest+ strict parser/validator (YAML frontmatter + markdown body);to_agent()→ catalog-expanded Agent with traits. (pyyaml added to deps.) - ✅
personas/registry.py—PersonaRegistry: builder-backed core (Code/Chat/Cowork keep exact prompts) + markdown built-ins; lifecycle (enabled/surfaced/default) persisted to<data>/personas.json; module singleton installed by the manager.agents/registry.get_agentdelegates here (lazy import; MyHelper still direct). - ✅ Ops persona shipped as a markdown manifest (
personas/builtin/ops.md) — dogfoods the parser. - ✅ Agent traits (
family/messaging/connectors) replace theagent.name == …branching inbuild_engine+manager.get_engine(so Ops behaves like Cowork: orphan scratch, connectors, scheduling, request_directory). Session binding/pin/rename ride existingSessionRecordfields. - ✅ REST:
GET /v1/personas,POST /v1/personas/{id}(enable/surface/default);/v1/agentsnow returns the surfaced persona picker. - ⬜ GUI deferred to a visual-review pass (persona picker + Personas settings tab) — backend
- endpoints ready; React UI to be built with Rohit per the visual-review rule.
Completion gate (tests): ✅ 28 passed.
test_persona_manifest.py— valid parse + 8 rejection cases (no/again frontmatter, missing id, empty body, unknown tool, bad family/workspace/mode);to_agenttraits + tools.test_persona_registry.py— lifecycle + set-default; Cowork-disabled → fallback; surface toggle filters the picker but keeps the persona resolvable; state persists.test_builtin_personas.py— Code/Cowork personas resolve to the exact builder toolsets; Ops composes the knowledge toolset.test_session_persona.py— persona recorded on the session + stable across reload; pin + rename persist.- Full suite: 404 passed (1 prior test updated: unknown-agent fallback is now the default persona Cowork, not Code); same 3 pre-existing SDK-import errors.
Phase 2 — Distribution + autonomy + in-app Inbox
Goal: third-party personas (local dir / git URL) with consent; effective-risk overrides; Unattended + suspend/resume + self-wake; the in-app Inbox.
- ✅
personas/loading.py+ registryinstall_from_dir/install_from_git(injectable clone);consent_summary; installed sources persist + reload; third-party personas land disabled+unsurfaced pending consent. RESTPOST /v1/personas/install. - ✅
overrides.py— user-localRiskOverrideStore(glob, most-specific wins), wired intobuild_engine's PermissionEngine. Never written by persona loading (no-self-grant). - ✅
unattended.py— per-session toggle registry (persisted). RESTPOST /v1/sessions/{id}/unattended. (Composer-disable + one-tap confirm = GUI, deferred.) - ✅
inbox.py—InboxStore(3 kinds),pending→resolvedstate machine (idempotent, first-responder-wins),reconcile_on_resume, asyncinbox_approver. RESTGET /v1/inbox,POST /v1/inbox/{id}/resolve,GET /v1/inbox/reconcile. - ✅
selfwake.py—WakeStore(timer + on-completion) +due()/complete_job()+ thesleep_for/sleep_until/wake_ontools. - ⬜ Live wiring (carried forward): approver-swap when Unattended; scheduler resuming due wakes; GUI.
Completion gate (tests): ✅ 25 passed.
test_persona_loading.py(5) — dir + git-URL load; consent summary; disabled-pending-consent; persistence; invalid manifest fails loud.test_risk_overrides.py(6) — most-specific glob wins; relaxes MCP in classify + engine; persistence; tighten direction; no-self-grant (manifest override/elevated-mode ignored).test_inbox.py(7) — kinds/filter; idempotent first-responder-wins; persistence; reconcile; approver allow/deny.test_unattended.py(2) — toggle + persist; unattended routes approvals to the Inbox.test_self_wake.py(5) — timer due-after-fire; completion due-after-complete; mark_fired; persistence; tools.
Phase 3 — Channels + events + marketplace
Goal: Inbox mirrored to Slack/Telegram (bidirectional); on-event wake; registry/marketplace; white-label builds.
- ✅ Marketplace = dir/GitHub load + snapshot (see decision above):
install_from_dirnow copies manifests into<data>/personas-installed/<id>/(stable, source-independent);install_from_gitclones then snapshots. Built-ins bundled/immutable. - ✅
inbox_routing.py— named inboxes + bindings (in-app / Slack / Telegram);route_for(session override > persona default > default);deliver(embeds[ocw:<id>]);resolve_from_reply(correlate inbound reply → resolve). RESTGET /v1/inbox/routing,POST /v1/inbox/routing/binding. - ✅ On-event wake:
WakeStore.add_event/fire_event+ thewake_on_eventtool. - ⬜ White-label build tooling — deferred.
- ⬜ Live wiring + GUI — see Current status (the remaining work).
Completion gate (tests): ✅ 13 passed (+ snapshot test).
test_inbox_routing.py(7) — route precedence; persisted bindings; deliver embeds item id; in-app-only delivers nothing; inbound reply resolves the right item (approve / free-text); tokenless reply ignored.test_self_wake.py— on-event due-after-fire + thewake_on_eventtool.test_persona_loading.py— snapshot survives source deletion.- Full suite: 438 passed, 1 skipped, same 3 pre-existing SDK-import errors.
Phase 4 — UX / IA polish (built 2026-06-27)
Goal: make the persona model feel coherent in the GUI. All frontend (+ one backend field); design
settled in PERSONAS.md (Family, Sidebar IA) + PERMISSIONS-AND-INBOX.md (attention/liveness).
- ✅ Family-aware frontend — Sidebar Projects/grouping keys off
familyOf(id) === "code"; App'sneedsWorkspace/gatesWorkspacenow read the persona'sneeds_workspace/family === "code"(id-based fallbacks only until personas load). A code-family third-party persona now gates a folder like Code; a knowledge persona starts orphan like Cowork. - ✅ Sidebar default-first + Chat hidden — registry: Cowork registered first; Chat
default_surfaced=False. Verified live: picker = OpenCoworker, Code, Ops; no Chat. - ✅ Expand ≠ switch — header toggles the accordion only (
browseKey = openKey); chat area changes on select/New. Verified live. - ✅ Attention badges —
list_sessionsnow returnsattention(count of pending Inbox items for the session). Amber count renders on the session row → persona header → footer Inbox (views of the one Inbox queue; no new tab). Verified with seeded data; honest path needs an Unattended session (items only reach the Inbox when Unattended — attended sessions answer inline). - ✅ Liveness dot —
list_sessionsreturnsliveness(working= in-flight turn viais_running,sleeping= a self-wake pending viawakes.pending, elseidle). Count-less dot (green pulse / grey); never bubbles into the attention count. - ✅ Pinned band — cross-persona band above the accordions, manual pins only; pin/unpin on every session row.
- ✅ Shared top zone — Search + Integrations + Automations in
.shared-navabove the persona accordions (Search moved to top, first). - ✅ Inbox → session link (answer-in-context, "B") — each Inbox item shows a clickable chip
(persona icon + session title) that opens its originating session; and the session view renders
its pending Inbox item inline above the composer (
InboxItemCard, shared withInboxView), so the blocking question/approval is answerable in context — resolving the same item id (first responder wins). Decided not to add the reverse (clickable badge → filtered Inbox) this pass. - ⬜ Carried deferred polish: composer-disable while Unattended + reconciliation on turn-off (real behaviors Rohit reaffirmed: Inbox items exist only for Unattended sessions; turning Unattended off surfaces pending items inline before the session proceeds); inbox routing config UI; reactive persona refresh after enable; clickable attention badge → filtered Inbox.
Completion gate: typecheck + npm run build green ✅. Visual: dots + Inbox badges seen live by
Rohit; the Inbox→session link + inline answer-in-context to be shown next review with a real
Unattended session (not synthetic seeding).
Design item (not Phase 4) — per-coworker integrations
Separate Connect (authenticate a connector — global, the one Integrations page common to all)
from Grant (which connected integrations a coworker may use — per-persona). Evolve the
manifest connectors from a boolean to a list of connector ids the persona wants; a coworker
sees only connectors it declares AND that are connected. Surface a user override in the Personas
tab (per-persona connector toggles, with a "Connect" deep-link to the global page). Design later.
Known bug — phantom deepseek-chat in the model picker (TABLED 2026-06-28; fix architecturally)
deepseek-chat shows in the composer model selector though there's no DeepSeek provider. Root: a
bare deepseek-chat in prefs.json (added under the OpenAI provider — bare = OpenAI; almost
certainly a pre-#340 default), and _model_provider defaults any bare name to openai, which
is configured → the filter keeps it.
The deeper cause (why we tabled the quick fix): OpenAI-compatible services (DeepSeek, OpenRouter,
vLLM, Azure) are modeled as "OpenAI provider + a custom base_url", so their models live in the
OpenAI bucket and are stored bare. A bare name is therefore ambiguous — junk on stock
api.openai.com, but a valid model on a custom endpoint. You can't tell from the name.
A targeted patch was built + reverted (Rohit's call 2026-06-28): hide a bare name on stock OpenAI
unless it matches an OpenAI naming family (gpt-/o…/KNOWN_MODELS), allow anything when a custom
base_url is set (it correctly used base_url as the stock-vs-compat discriminator, hide-not-delete).
Correct for today's design, but a heuristic on an ambiguous model.
Decided fix (later, comprehensive): make OpenAI-compatible services first-class providers —
distinct descriptors (DeepSeek / OpenRouter / …) each with its own key + base_url + model list — so a
model is explicitly provider:model and only shows when that provider is configured. Zero
bare-name ambiguity. Work: new descriptors over the openai-compat build, GUI panes, and a migration
for existing bare names. Until then the phantom is a harmless cosmetic leak; current behavior kept.
Session log
Append one entry per working session (newest at top): date · who/branch · what changed · tests.
-
2026-06-29 · Devika /
platform/personas· UI-Refresh — "New project" regression fix + Codex-style Projects. Rohit flagged that the sidebar "New project" line did nothing when clicked, and shared a Codex screenshot of its Projects panel. (1) Regression (introduced withisProjectScoped): the button only set the gate flag, but the gate renders behindsurface==="session" && gatesWorkspace(activeAgent)— so it silently no-op'd whenever the active session wasn't project-scoped (e.g. browsing Code/Ops from a Cowork session). Fix:onNewProject(browseKey)→newProject(persona)switches to that persona, starts a fresh session, and opens the gate in create mode. Verified live:+from a Cowork session switched to Ops and showed the "New project" create gate. (2) Codex-style Projects (Option A, agreed with Rohit — keep persona-first, polish the per-persona Projects sub-section): "New project" moved from a list row to a+in the Projects header; each folder is now collapsible (active project open by default, else the most-recent); session rows carry a right-aligned compact age (compactAge: now/5m/6h/3d/2w/4mo/2y); folders truncate to 5 with a "Show more" disclosure; search expands matching folders and hides the rest.tsc+buildclean, vitest 20/20. Commits60f2d44(regression),2d016c5(Projects). Note: the lone existing Ops folder shows a scratch-dir hash (4cfb0fb3-46a) — pre-change orphan data; new projects get the real folder name. -
2026-06-29 · Devika /
platform/personas· UI-Refresh visual-parity — post-review fixes (Rohit's eyeball). After Rohit reviewed the Tailwind port: (1) Sources bar was invisible — it rendered as a sibling before the workspace, so it sat at y=0 behind theposition:absoluteglass topbar; moved it inside the chat column (which already pads to clear the topbar) as a fixed sub-header. Now SOURCES + the connector avatar stack show under the title and open the Session-connections drawer. (2) Connector cards confirmed working — drove a live FakeSlack message into a subscribed session; it rendered as a brand-tinted card with resolved names (ocw-test/Alex Riveravia the real users.info/conversations.info). The "legacy-looking" messages were pre-Phase-2 data (nosource); new ones card correctly. (3) Markdown restyle — visible list markers + real item/heading spacing (lists were run-together), zebra/tinted tables, refined inline code. (4) Dropped the redundant per-persona "New session" (top split button covers it; mock omits it). (5) Fixed the persona/ drawer icon rendering a raw logo-id (cowork) instead of a glyph.tsc+buildclean, vitest 20/20. Commits776737a,abeaba0(+ the surface ports). Note: a demo connector message + a denied turn were left in the localhisession by the live card test — delete if unwanted. The_say is not a valid argumentSlack log is benign (message delivered, names resolved). -
2026-06-29 · Devika /
platform/personas· UI-Refresh visual-parity pass — port the GUI to the mock (Tailwind). Rohit's feedback after the phases landed: functional but "a far cry from the mocks." Root cause: the app andui-mocks/redesign.htmlshare the exact CSS tokens, but the mock is Tailwind and the app was hand-CSS, so per-component polish was never ported. Added Tailwind v3 (config mirrors the mock; color tokens wrapped incolor-mixso the mock's/NNopacity utilities work; imported before styles.css so un-ported surfaces keep their CSS). Then ported all four surfaces to the mock markup, surface by surface, each screenshot-verified in the running app: session view (topbar pills, sources bar, brand-tinted connector cards, StepGroup, composer card, dark bubbles; dropped the dotted main bg) · sidebar (wordmark + layout toggle, split button + persona menu, pinned/recent + grouped cards, bottom nav, footer) · persona page + sources drawer (identity/enable, recommends with connect state, default-connection toggles; logo-id icons now render a glyph) · Integrations (Connectors/Messaging/Activity/MCP sub-nav + connector-card grid with real brand badges). Behaviour unchanged throughout;tsc+buildclean, vitest 20 passed at every step. 6 commits, local-only, Devika-authored. Not visually verified (code-complete + test-covered): the live connector card (needs a real connector message), the Sources drawer (needs opening), the Ops persona's recommends sections (needs an Ops session to navigate to). Dev note: the tailwind.config change needs a dev-server restart — a stale vite won't show it (review on a freshly-started dev server). -
2026-06-29 · Devika /
platform/personas· UI-Refresh Phase 5 — frontend polish + the e2e merge gate. Frontend (§7): Integrations restructured into a left sub-nav (Connectors · Messaging routing · Activity · MCP); a dual sidebar (flat ↔ grouped-by-persona, persisted via a newnav_layoutpref, grouped cards carry a gear → PersonaView); a New-session split button (primary = last persona, ▾ = enabled personas + "Manage personas…"); and aStepGroupthat collapses tool/approval items into "N actions · M approvals ✓".ConnectorBadge(real brand color/logo) now also renders on the connector rows. Backend:nav_layoutpref (GET /v1/settings+POST /v1/settings/nav-layout). Product bug found + fixed by the e2e: the asyncSlackAdapter.send/send_interactivewrapped the blocking httpx senders and were awaited directly on the server loop (mirror/interaction paths) — freezing all sessions/sockets for the Slack round-trip; now offloaded viaasyncio.to_thread(matches the engine's send path). The merge gatetest_ui_refresh_e2e.pydrives the WHOLE refresh against FakeSlack with the real SlackAdapter/slack_bolt stack: connect → channel message becomes a structured connector card with names resolved via realusers.info/conversations.info(provider gets framed text, nosource) → Unattended approval mirrored as a real Block Kit card → inject Approve → durable resume + reply posts back → mute drops delivery (still buffers) → attention == unconnected recommends. Tests: e2e + nav_layout → Python 500 passed (3 pre-existing SDK errors); vitest 20 passed;tsc+buildclean. State-dir isolated (real secrets hash byte-identical across a full-suite run). Local-only, Devika-authored. UI-Refresh Phases 1–5 + FakeSlack COMPLETE. Remaining: Rohit's visual review of the GUI; deferred polish (Integrations 2-col card grid; connector brand color on the transcriptConnectorMessageCard; sessiondetailchannel names need the adapter cache); push (Rohit). -
2026-06-29 · Devika /
platform/personas· UI-Refresh Phase 4 — persona + session connection surfaces. Backend (5 endpoints):GET /v1/personas/{id}(identity + tools + recommended_models + permission mode + workspace +recommendswithconnectedannotated +default_connections);POST /v1/personas/{id}/ connections(set persona default);POST /v1/personas/{id}/enable(delegates to the registry);GET /v1/sessions/{id}/connections(effectiveconnectedw/ a detail string +recommendednot-yet-connectedattentioncount);POST /v1/sessions/{id}/connections(session override,clearto inherit). Frontend:PersonaView(identity + Enable toggle + capabilities + recommends + "new sessions get by default" toggles),SourcesBar(avatar stack + ⚠ N) +SourcesDrawer(per-session connection toggles), a sharedToggle, andconnectors/visuals.tsthreading real brand_color/logo from/v1/connectorsinto these surfaces (closes the Phase-2 neutral-gray follow-up for the connection surfaces; the transcriptConnectorMessageCardis still neutral — its caller doesn't thread a color yet). SourcesBar mounted under the session topbar; PersonaView reachable via a topbar "About this persona" button + the drawer link (grouped-nav gear + New-session "Manage personas…" entry = Phase 5). Design call (per Phase-3 review): personadefault_connectionslists the recommended defaults (spec-literal); the sessionconnectedlist shows ALL effective connectors (honest where "why is this on" matters). Tests:test_persona_connections.py(5, isolated viaCOWORKER_STATE_DIR) → Python 498 passed (3 pre-existing SDK errors); vitest 17 passed;tsc+buildclean. Test-hygiene fix (separate commit): isolatedtest_connections.py(Phase 3) — it built a realSecretStoreagainst the global state dir and wrote a fakegithub:defaultinto the developer's real~/.config/coworker/secrets.json. Now pinned to a tmpCOWORKER_STATE_DIR(verified: global secrets hash unchanged across a run). Known/flagged (pre-existing, needs Rohit): other tests (test_attachments,test_mcp, historicallytest_connectors) still write to the real state dir; a fakegithub:default(ghp_test) currently sits in the real secrets store (deletion was correctly blocked — pending Rohit's call). Deferred: sessiondetailshows chat ids (names need the live adapter cache);recommendedexcludes mcp. Next: Phase 5 (frontend polish + e2e gate).
-
2026-06-29 · Devika /
platform/personas· UI-Refresh Phase 3 — connection hierarchy (load-bearing data model). Newconnections.py:PersonaConnectionStore(per-persona default on/off, seeded from manifestrecommends)SessionConnectionStore(per-session overrides) + a pureeffective()resolver — connected AND (session-override if present, else persona-default, else inherit-on). Manager owns both stores +effective_connectors(session); runtime gating in two places: connector tools (aconnector_filterthreaded intobuild_engine, applied in BOTHget_engineand_build_task_engine) and inbound delivery (_dispatch_inboundchannel + DM paths skip a muted connector but still buffer).delete_sessiondrops the session's overrides. Adversarially reviewed before commit — the resolver was proven correct; the review caught + we fixed: (1) MAJOR seed-staleness — atier:coreconnector seeds on regardless of current connectedness (effective()'s connected-gate is the single source of truth), so it self-lights when later connected instead of being frozen off (intentional deviation from §4.2's literal wording, documented in code); (2) unified the inbound gate ontoeffective_connectors(was a seed/no-seed asymmetry); (3) the scheduled-task engine builder now also applies the filter. Tests:test_connections.py(8, incl. connect-after-seed self-lighting + the muted-not-delivered/tools-absent/DM-muted gates) → Python 493 passed (3 pre-existing SDK errors). Local-only, Devika-authored. Phase 4 design note (decided, to implement in §5): since unrecommended-but-connected connectors inherit on, the persona-detaildefault_connectionswill enumerate all connected connectors with their effective default (keep inherit-on, make the UI honest) rather than special-casing built-in personas. Next: Phase 4 (persona + session connection surfaces).
-
2026-06-29 · Devika /
platform/personas· UI-Refresh Phase 2 — structured connector messages. Connector inbound messages now carry a display-onlysourcesidecar so the GUI renders a rich card while the model still gets the framed text. Backend:MessageSourcedataclass (§3.1);engine.run(..., source=)stores it on the user message and emits it inTURN_START;_outbound_messagesstripssourceunconditionally (the sole provider feed — proven by a no-context strip test);deliver_to_session+_dispatch_inboundbuild+thread the source on both channel and DM paths (steering path too); persisted verbatim (store json-dumps each message) soGET /messages+ WSturn_startsurface it for free. Frontend:ConnectorMessageCard(brand-tinted header/edge, names with id-on-hover swap, relative time); extracteditemsFromMessages.ts(testable) mapssource.connector→ aconnectoritem;Transcriptrenders it. Tests:test_message_source.py(5, incl. persisted-and-stripped) → Python 485 passed (3 pre-existing SDK errors); vitest 11 passed;tsc+buildclean. Updated 3 existing tests for the newdeliver_to_session/_outbound_messagescontract (signatures +is→==). Local-only, Devika-authored. Deferred (small follow-up, tracked for Phase 4): connector cards render in neutral gray —sourcecarries only the connector id, notbrand_color; abrandColorprop hook exists to thread the real color (from/v1/connectors, which Phase 1 added) once the session view loads connector data. Next: Phase 3 (connection hierarchy — the load-bearing data-model change). -
2026-06-29 · Devika /
platform/personas· UI-Refresh Phase 1 — connector registry metadata + contract. Backend:ConnectorDescriptorgainsbrand_color+logo;connector_listsurfaces them; placeholderavailable:falsedescriptors for the not-yet-shipped recommended connectors (datadog/salesforce/pagerduty — github+hubspot already ship here, so they got brand metadata, not duplicate placeholders);SlackAdaptergains cached_channel_name(conversations.info) + publicresolve_user_name/resolve_channel_namewrappers, and populates a newSessionSource.chat_namein_on_message(mirrorsuser_name). Frontend:src/connectors/registry.tsx(logo-id → inline SVG +FALLBACK) andConnectorIcon/ConnectorBadge(brand color from API, fallback plug on unknown id);Connectorinterface gainsbrand_color/logo. Stood up a minimal vitest + @testing-library/react harness (separatevitest.config.ts, devDeps only — reused by later frontend phases). Tests:test_connector_registry.py+test_slack_resolve_channel_name(Python 480 passed, 3 pre-existing SDK errors); vitest 4/4;tsc --noEmit+npm run buildclean. Local-only, Devika-authored, not pushed. Next: Phase 2 (structured connector messages). -
2026-06-29 · Devika /
platform/personas· UI-Refresh: FakeSlack harness (foundation, build-first). Builtcoworker/testing/fake_slack/— an in-process Slack test double on Starlette+uvicorn (ephemeral port): Web API (auth.test/apps.connections.open/users.info/conversations.info/chat.postMessage/chat.update), Socket Mode WS speaking real-slack_bolt-shapedevents_api+block_actionsenvelopes, a/control/*HTTP API + programmaticFakeSlackobject, and a standalonepython -m coworker.testing.fake_slackrunner. One production change: aSLACK_API_URLbase-URL override on the Slack adapter (boltAsyncWebClient) and both httpx senders (default = real Slack).aiohttp>=3.9declared in themessagingextra (so CI installs it) + pip-installed into the test venv. Added thefake_slackpytest fixture (tests/conftest.py, monkeypatched env). Tests:test_fake_slack.py6/6 incl. a guard that the realAsyncSocketModeHandlerdispatches both fake-sent envelope shapes; full suite 477 passed (3 pre-existing SDK-import errors). Local-only, Devika-authored, not pushed. Unblocks UI-Refresh Phases 1–5 (the integration/e2e tests run the real adapter against this fake). -
2026-06-28 · Devika /
platform/personas· Live Slack re-test + 5 follow-up fixes. Ran the full messaging re-test end-to-end against real Slack (after re-auth). Verified live: super-agent retired; live channel reaction (per-session event bus — the original bug); error visibility (Unrouted panel); DM routing (park + deliver); allow-list/recent-senders UI; real inbound→reply round-trip; and the marquee Unattended approval → Slack Block Kit buttons → durable resume (needed Slack "Interactivity" enabled). Added a new "Unattended approvals → channel" routing UI (99a8748) and fixed bugs the test surfaced: reconnect clobbering a stored token/allow-list with the masked placeholder (d3cb0a2), recent senders showing "unknown" → resolve via users.info (b978044), approval card missing tool args (b4a822d), and Slack setup docs missing the Interactivity toggle + users:read (c6f4669). Each fix has tests; full suite 466 passed (3 pre-existing SDK errors). Note: an env-gatedCOWORKER_DEBUG_INJECTendpoint (feeds the real inbound path without a live bot) rode along inb4a822d; off by default. Remaining: push (Rohit). -
2026-06-28 · Devika /
platform/personas· Messaging refactor — 5 fixes (4 commits). Acting on the gaps the live Slack channel-subscription test surfaced. Fix 1 per-session event bus (_session_clients+broadcast_session): background turns (channel delivery, self-wake, durable resume) now stream live to any open socket instead of being discarded;turn_startsurfaces the inbound message as a user item. Fix 2 error visibility:unrouted.pydead-letter store — background-turn ERRORs + undeliverable inbound are logged + parked (GET /v1/unrouted+ Integrations panel) instead of vanishing. Fix 3 allow-list onto the (reachable) Connectors tab:/v1/connectorsnow carriesallowed_users+ recent senders; ported the allow/recent UI from the orphaned super-agent view. Fix 4+5 DM routing (prefsdm_session+/v1/messaging/dm-route; DM → designated session or parked) and retired the super-agent (deletedconnectors/superagent.py, the_sa_*surface,/ws/superagent,SUPERAGENT_SESSION_ID,SuperAgentView;myhelperkept resolvable). Tests:test_session_events.py,test_connectors_allowlist.py,test_dm_routing.py+ updated subscriptions/connectors (462 passed, 3 pre-existing SDK errors). GUI tsc clean. Remaining = Rohit's visual review + live Slack re-test (per the plan's E2E checklist). -
2026-06-27 · Devika /
platform/personas· Messaging↔sessions design + plan (pre-compact). WroteMESSAGING-AND-SESSIONS.md(channel subscription vs Inbox; one-bot-identity mention model;ask_user; durable correlation best-effort→hardened). Added the "RESUME HERE" remaining-work plan to Current status. Also committed this session: shared top zone (Integrations/Automations) + Pinned band (code-complete, not screenshot-verified — browser dropped). No new tests this entry. -
2026-06-27 · Devika /
platform/personas· Phase 4 first fixes (verified live). Sample persona~/coworker-personas/research-analyst.md(for manual install demo). Backend: default- first ordering (Cowork registered first) + Chat hidden by default (default_surfaced). Frontend: family-aware Projects (familyOf(id)==="code"), expand≠switch (browseKey),startNewSessiontakes a persona. Verified in the running app (screenshots saved): picker = OpenCoworker/Code/Ops (no Chat); Code→Projects, OpenCoworker/Ops→Recents; expanding Code/Ops keeps the OpenCoworker chat loaded. Tests: persona/server updated (Python 445 passed, 3 pre-existing SDK errors); GUInpm run buildgreen. Remaining Phase 4: Pinned band, attention/liveness badges, family-aware App gating — stopped here for Rohit's manual review. -
2026-06-27 · Devika /
platform/personas· UX pass (design + spec only). Settled the family model (binary workspace model; roles compose; Code stays a persona; frontend must be family-aware), the "Pinned band on top" sidebar IA (expand≠switch, default-first, Chat hidden, pins = pure accessibility, no "always-on" mode), and attention-vs-liveness sidebar indicators (attention count bubbles to the Inbox; liveness is a count-less dot; no new tab). UpdatedPERSONAS.md+PERMISSIONS-AND-INBOX.md+ added Phase 4 here. No code yet. -
2026-06-27 · Devika /
platform/personas· First-pass visual review + 2 fixes. Drove the running app: all 4 GUI surfaces verified. Fixed the Unattended toggle to read its persisted state on mount (addedGET /v1/sessions/{id}/unattended) and removed the redundant Settings Surfaces toggles (superseded by the Personas tab). Verified live; typecheck + server tests green. Remaining deferred polish: composer-disable while unattended; inbox routing config UI; reactive persona refresh after enable; inbox unread badge. -
2026-06-27 · Devika /
platform/personas· GUI pass done (4 commits: Personas tab + api client; persona-driven picker; Inbox view; Unattended toggle).npm install+ fullnpm run build(tsc + vite) pass. All phases 0–3 complete. Remaining = Rohit's visual review + deferred polish. -
2026-06-27 · Devika /
platform/personas· Phase 3 wiring done (3 commits: unattended approver-swap; self-wake resume busy→steer/idle→run via scheduler extra_tick; gateway inbound correlation + outbound channel mirror). Full suite 444 passed. -
2026-06-27 · Devika /
platform/personas· Phase 3 backend done (2 commits: snapshot install; on-event wake + multi-inbox routing). Marketplace decided = dir/GitHub load + snapshot (no hosted service). Addedinbox_routing.py,WakeStoreevents, manager routing store + REST. Tests: inbox_routing/self_wake/loading (13 + snapshot; full suite 438 passed). Remaining: live wiring (approver-swap, scheduler-resume, gateway inbound) + GUI pass. -
2026-06-27 · Devika /
platform/personas· Phase 2 backend/logic done (2 commits: 2a overrides+loading, 2b inbox+unattended+self-wake). Addedoverrides.py,personas/loading.py,inbox.py,unattended.py,selfwake.py; manager stores + REST. Tests: loading/overrides/ inbox/unattended/self-wake (25 passed). Live approver-swap + scheduler-resume + GUI carried forward. Next: Phase 3 — ask Rohit about marketplace before building. -
2026-06-27 · Devika /
platform/personas· Phase 1 backend done. Addedpersonas/(manifest + registry), Ops markdown persona, Agent traits replacing name-branching in build_engine/manager,/v1/personasendpoints. Tests: manifest/registry/builtin/session (28 passed; full suite 404 passed, 3 pre-existing SDK errors). GUI (picker + settings) deferred to a visual-review pass. Next: Phase 2. -
2026-06-27 · Devika /
platform/personas· Phase 0 done. Addedrisk.py+catalog.py; refactoredpermissions.pyto risk classes and Code/Cowork to build via the catalog. Tests:test_catalog.py,test_permissions_risk.py(41 passed; full suite 348 passed, 3 pre-existing SDK errors). Next: Phase 1 manifest + registry. -
2026-06-27 · Devika /
platform/personas· Wrote design docs (PERSONAS.md,PERMISSIONS-AND-INBOX.md) + this ledger. No code yet.