diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 5d068c74..e69de29b 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,67 +0,0 @@ -{"id":"bd-14p","title":"CI: add a PR-level docs-build (vitepress) check","description":"Docs build (docs.yml + marketing-deploy.yml) runs only on push to main, so doc breakage is invisible at PR time — ADR 0048/0049 broke the build for a full release cycle (v0.36.0→v0.38.0). Add a pull_request-triggered build-only docs:build job on the Namespace runner. GH issue #976.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-13T17:43:58.875957Z","created_by":"kj","updated_at":"2026-06-13T17:59:08.118248Z","closed_at":"2026-06-13T17:59:08.117829Z","close_reason":"Shipped in #977 — PR-level vitepress docs build now runs on docs/** + workflow changes","source_repo":".","compaction_level":0,"original_size":0,"labels":["ci","docs"]} -{"id":"bd-16n","title":"Chat: native multimodal (vision) image pass-through","description":"Remaining bd-39m. When the active model is vision-capable, send an attached IMAGE as a native A2A image part (base64 data URI) instead of the pipeline. Needs: model.vision capability flag (config), composer routes images→image part when vision on, executor builds a LangChain multimodal (image_url) message. User is provisioning a vision model on the gateway. Touches executor.py + ChatSurface + config.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T06:16:06.224476Z","created_by":"kj","updated_at":"2026-06-14T07:10:11.397005Z","closed_at":"2026-06-14T07:10:11.396202Z","close_reason":"shipped #1009: native vision — model.vision flag, executor image-part extraction → multimodal HumanMessage, composer sends images native. Verified live (deepseek-v4 read a test image).","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","multimodal"]} -{"id":"bd-1iw","title":"Background subagents Phase 3: chat UX (jobs panel + status pill)","description":"ADR 0050 Phase 3. A live status pill (⟳ N agents running), a background-jobs panel (status/elapsed/stop, modeled on FleetTurnWatch + cc-2.18 BackgroundTasksDialog), completion toast + unread badge, and a rich live subagent card in-transcript. Check @protolabsai/ui/ai (Conversation/Message/ToolCall) first. Phase 1 already ships the BackgroundWatch live in-chat injection (#996).","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T04:56:04.819132Z","created_by":"kj","updated_at":"2026-06-14T05:35:57.321159Z","closed_at":"2026-06-14T05:35:57.320755Z","close_reason":"Shipped pill + jobs dialog + unread badge (UtilityBar BackgroundJobs widget). Live per-tool progress card deferred → needs a background.progress channel (new bead).","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0050","background-subagents","frontend"]} -{"id":"bd-1n7","title":"Chat: file-only send (no text required)","description":"Remaining bd-39m. The DS PromptInput send button enables only on non-empty text, so a file-only message can't be sent. Needs a DS update (PromptInput canSubmit also true when attachments present) → release → protoAgent bump + allow file-only send. Small.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-14T06:16:06.451545Z","created_by":"kj","updated_at":"2026-06-14T07:52:47.662222Z","closed_at":"2026-06-14T07:52:47.661794Z","close_reason":"shipped #1012: file-only send — canSend allows ready-attachment-only; @protolabsai/ui 0.34 attachment-aware submit (protoContent #234). 101 e2e green.","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ds","ui"]} -{"id":"bd-1o9","title":"Scheduler gives up forever if jobs.db owner-lock is briefly held at boot — no retry (waits/schedules silently never fire)","description":"Found driving the live agent. After a restart the new server logged 'jobs.db ... already owned by another live instance — not starting the scheduler', and the scheduler stayed OFF for ~16 minutes — wait/schedule_task jobs never fired (a wv1 wait sat overdue with empty last_fire) — until a config hot-reload happened to re-init the scheduler and it finally acquired the lock.\n\nROOT CAUSE: scheduler/local.py::LocalScheduler.start() calls _acquire_jobs_lock() exactly ONCE; on failure it logs + returns (no retry). The lock is a non-blocking fcntl.flock. A brief overlap is common: a restart/redeploy where the OLD process frees the port (so a port-wait restart proceeds) but stays alive a bit longer draining an in-flight turn keeps holding the flock. The new server's scheduler refuses at boot and never retries, so it only recovers if a later hot-reload/restart re-runs start().\n\nIMPACT: a normal restart (or Docker redeploy with momentary container overlap, or watchtower) can leave the agent running with NO scheduler — ADR 0053 wait resumes and all scheduled tasks silently don't fire — indefinitely, until a config change happens to re-init it.\n\nFIX: when the lock is held at start(), retry in the background (e.g. every ~15s) until acquired (or stop()), then recover-missed-fires + start the poll loop. A genuinely co-located instance just keeps waiting harmlessly (throttle the warning). Recovery becomes seconds, not 'until the next hot-reload'.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-15T01:27:04.646638Z","created_by":"kj","updated_at":"2026-06-15T01:34:27.070600Z","closed_at":"2026-06-15T01:34:27.070174Z","close_reason":"Fixed in PR #1041 (merged efabaefd): start() retries the owner-lock in the background instead of giving up.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0004","robustness","scheduler"]} -{"id":"bd-1rn","title":"Chat UX: DS message-thread adoption (Conversation/Message/MessageActions)","description":"Remaining bd-39m. Replace the hand-rolled message list/bubbles with DS Conversation (auto-scroll) + Message + MessageActions (copy/regenerate). Wrap our Markdown/ToolCalls/Reasoning inside Message. RISK: touches the chat-streaming/self-heal invariants (#613/#615) — do carefully. Touches ChatSurface.tsx.","status":"closed","priority":3,"issue_type":"feature","created_at":"2026-06-14T06:16:05.998590Z","created_by":"kj","updated_at":"2026-06-14T07:47:05.918578Z","closed_at":"2026-06-14T07:47:05.917923Z","close_reason":"shipped #1011: DS Conversation/Message/MessageActions + copy/fork/regenerate; #613/#615 invariants intact, 100 e2e green.","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ds","ui"]} -{"id":"bd-1sr","title":"Background subagents: live progress card + background.progress channel","description":"Follow-up to ADR 0050 Phase 3. Stream a detached background job's tool-by-tool progress: publish background.progress on the bus from the bg turn's executor frames, and render a rich live subagent card in the transcript (à la protocli AgentExecutionDisplay / @protolabsai/ui/ai ToolCall). Phase 3 shipped the pill + jobs dialog + unread badge; this adds the live progress feed it can't show today.","status":"closed","priority":3,"issue_type":"feature","created_at":"2026-06-14T05:35:57.545881Z","created_by":"kj","updated_at":"2026-06-14T06:05:58.297130Z","closed_at":"2026-06-14T06:05:58.296717Z","close_reason":"Shipped: executor progress hook → background.progress bus channel → live per-tool feed in the jobs dialog (ADR 0051).","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0050","background-subagents","frontend"]} -{"id":"bd-1tf","title":"Goal mode: testable-outcome goals with self-driving completion loop","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-05-28T08:17:34.692753012Z","created_by":"claude","updated_at":"2026-05-28T08:32:33.133003808Z","closed_at":"2026-05-28T08:32:33.132409061Z","close_reason":"Goal mode shipped: testable-outcome goals (pluggable verifiers) with self-driving continuation loop, persisted checklist, and unachievable/exhausted escape hatches. Mirrors protocli's goal system but verifier-backed + disk-persisted for the long-running server. 30 tests; suite 554 green.","source_repo":".","compaction_level":0,"original_size":0} -{"id":"bd-1tf.1","title":"Goal state + disk store (per-session, sandbox→home fallback)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-28T08:17:41.306915033Z","created_by":"claude","updated_at":"2026-05-28T08:32:32.308214681Z","closed_at":"2026-05-28T08:32:32.307896403Z","close_reason":"graph/goals/types.py (GoalState, VerifyResult) + store.py (per-session disk persistence, GOAL_PATH→/sandbox→~/.protoagent fallback, atomic write, session-id sanitize).","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1tf.1","depends_on_id":"bd-1tf","type":"parent-child","created_at":"2026-05-28T08:17:41.306915033Z","created_by":"claude","metadata":"{}","thread_id":""}]} -{"id":"bd-1tf.2","title":"Pluggable verifier registry (command/test/ci/data/llm)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-28T08:17:41.455125607Z","created_by":"claude","updated_at":"2026-05-28T08:32:32.468272023Z","closed_at":"2026-05-28T08:32:32.467944658Z","close_reason":"graph/goals/verifiers.py: command/test/ci(gh)/data(contains|restricted-expr)/llm(fallback) registry + run_verifier dispatch. Safe-builtins for data expr.","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1tf.2","depends_on_id":"bd-1tf","type":"parent-child","created_at":"2026-05-28T08:17:41.455125607Z","created_by":"claude","metadata":"{}","thread_id":""}]} -{"id":"bd-1tf.3","title":"GoalController: control parsing + decision loop (met/continue/exhausted/unachievable)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-28T08:17:41.598528308Z","created_by":"claude","updated_at":"2026-05-28T08:32:32.621536937Z","closed_at":"2026-05-28T08:32:32.621205284Z","close_reason":"graph/goals/controller.py: parse_control (/goal set/status/clear, json+plain) + evaluate decision matrix (achieved/continue/exhausted/unachievable, checklist carry, no-progress streak, give-up).","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1tf.3","depends_on_id":"bd-1tf","type":"parent-child","created_at":"2026-05-28T08:17:41.598528308Z","created_by":"claude","metadata":"{}","thread_id":""}]} -{"id":"bd-1tf.4","title":"Config goal block + server.py continuation loop + /goal endpoints","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-28T08:17:41.754744061Z","created_by":"claude","updated_at":"2026-05-28T08:32:32.784609123Z","closed_at":"2026-05-28T08:32:32.784258164Z","close_reason":"config goal_* block + from_yaml; server.py: _run_turn_stream helper (dedupes the event loop), goal continuation loop in both streaming + non-streaming paths, parse_control short-circuit, /api/goal/{id} GET+DELETE, controller wired in _init_graph.","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1tf.4","depends_on_id":"bd-1tf","type":"parent-child","created_at":"2026-05-28T08:17:41.754744061Z","created_by":"claude","metadata":"{}","thread_id":""}]} -{"id":"bd-1tf.5","title":"Tests + docs (configuration.md, goal-mode guide)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-28T08:17:41.902931862Z","created_by":"claude","updated_at":"2026-05-28T08:32:32.950437927Z","closed_at":"2026-05-28T08:32:32.950111063Z","close_reason":"30 tests (store/verifiers/controller) green; full suite 554. Docs: configuration.md goal section + guides/goal-mode.md + sidebar entry.","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1tf.5","depends_on_id":"bd-1tf","type":"parent-child","created_at":"2026-05-28T08:17:41.902931862Z","created_by":"claude","metadata":"{}","thread_id":""}]} -{"id":"bd-1vo","title":"Desktop: Windows code-signing identity (kill SmartScreen prompt + AV false-flags)","description":"GH tracking issue: protoLabsAI/protoAgent#924 (full research mirrored there).\n\nNSIS installer + PyInstaller sidecar ship unsigned: SmartScreen prompts on the installer, and AV occasionally false-flags the onefile sidecar. Signing BOTH artifacts fixes both.\n\nDECISION RESEARCH (2026-06-11, verified current):\n- RECOMMENDED: Azure Artifact Signing (renamed from \"Trusted Signing\"), Basic tier $9.99/mo (~$120/yr, 5,000 sigs, 1 cert profile). As of Apr 2026 NO 3-year-history requirement; orgs US/CA/EU/UK + individuals US/CA. Only CI-friendly option without extra fees (azure/trusted-signing-action; Tauri v2 custom signCommand).\n- Alternatives: EV cert ~$280-380/yr + cloud-signing add-on fees for CI (USB token can't live in hosted runners); OV ~$220/yr, weak SmartScreen; Certum open-source ~EUR70/yr but hardware-card = no hosted CI. CA/B capped trad certs at 459 days from Feb 2026 (recurring revalidation).\n\nNEEDS FROM ORG (blocking): (1) paid Azure subscription (free/trial excluded; pay-as-you-go w/ nothing else = just the $9.99), (2) one-time identity validation for protoLabs.\nTHEN (agent work): secrets into repo -> signing step on desktop-build.yml Windows leg (sign sidecar .exe BEFORE Tauri bundles it + the NSIS -setup.exe; Tauri v2 bundle.windows.signCommand), update apps/desktop README signing table.","status":"open","priority":2,"issue_type":"task","created_at":"2026-06-11T08:42:55.926020Z","created_by":"kj","updated_at":"2026-06-11T09:21:19.880191Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["desktop","windows"]} -{"id":"bd-1wt","title":"Slash-command precedence single-sourced: dispatcher + palette share one resolver","description":"Follow-up to the user's 'several sources of truth' observation. The chat dispatcher (server.chat._parse_workflow/subagent/skill_command) and the console palette (_operator_chat_commands) each encoded the workflow>subagent>skill precedence + shadow rules separately — same class as bd-2aa/bd-67j (tool inventory). Consolidated: server.chat._slash_kind() is the single precedence authority; the _parse_* fns and resolve_slash_commands() (which the palette now calls) both use it. Warn-once shadow logic lives in the resolver. Shipped with bd-67j.","status":"closed","priority":3,"issue_type":"chore","created_at":"2026-06-14T20:57:07.632065Z","created_by":"kj","updated_at":"2026-06-14T21:05:47.298956Z","closed_at":"2026-06-14T21:05:47.298473Z","close_reason":"Fixed in PR #1031 (merged): slash precedence single-sourced in graph/slash_commands.py.","source_repo":".","compaction_level":0,"original_size":0,"labels":["console","refactor","tools"]} -{"id":"bd-20c","title":"Background subagents Phase 4: control tools + auto-background","description":"ADR 0050 Phase 4. task_output(id, block, timeout) + stop_task(id) tools (cc-2.18 TaskOutput/TaskStop), and foreground→auto-background on a time budget so a long synchronous delegation transparently detaches and becomes killable — the direct cure for the audited runaway. Also consider per-subagent tool-allowlist scoping for background jobs (deferred from Phase 1).","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T04:56:05.040977Z","created_by":"kj","updated_at":"2026-06-14T06:05:58.045727Z","closed_at":"2026-06-14T06:05:58.045312Z","close_reason":"Shipped: task_output + stop_task (real CancelTask) + foreground→auto-background (BACKGROUND_AUTO_S) + cancel telemetry.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0050","background-subagents"]} -{"id":"bd-241","title":"Setup wizard: Storage/Workspace step is unclear","description":"Audit (fresh-start wizard): four unrelated concerns crammed into one terse 'Storage' step. (1) 'Knowledge DB' default shows the Docker path /sandbox/knowledge/agent.db which is silently rewritten to ~/.protoagent locally (misleading). (2) 'Knowledge top K' is retrieval tuning, not storage. (3) 'Project path' vs 'Allowed project directories' read as redundant; relationship (project auto-allowed; allowed=extra) only in fine print. (4) No inline validation: a non-existent project path is accepted then breaks beads/notes. Fix: regroup Knowledge vs Workspace, drop /sandbox default (optional + resolved placeholder), per-field hints, inline-validate project path exists.","status":"open","priority":1,"issue_type":"task","created_at":"2026-06-15T04:58:17.827223Z","created_by":"kj","updated_at":"2026-06-15T04:58:17.827223Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["console","setup","ux"]} -{"id":"bd-2aa","title":"set_goal tool advertised but NOT bound to the agent — create_agent_graph drops goal_enabled, so ADR 0028 agent-self-goal is dead","description":"VERIFIED live: the agent cannot self-set a goal. Forced a set_goal invocation over /a2a; the runtime returned:\n 'Error: set_goal is not a valid tool, try one of [current_time, calculator, web_search, fetch_url, ask_human, request_user_input, show_component, memory_*, schedule_task, list_schedules, cancel_schedule, wait, check_inbox, beads_*, read_note, write_note, append_note, board_*, st_*]'\n=> set_goal is genuinely absent from the model's bound toolset (note: wait/beads/inbox/notes/board/st_* ARE all bound).\n\nROOT CAUSE (one-line omission):\n- tools/lg_tools.py:808 def get_all_tools(..., goal_enabled=False) # default False\n- tools/lg_tools.py:849-850 'if goal_enabled: tools.append(_build_set_goal_tool())' (ADR 0028)\n- graph/agent.py:661 (inside create_agent_graph, the LIVE lead-agent build) calls:\n all_tools = get_all_tools(knowledge_store, scheduler=scheduler, inbox_store=inbox_store, beads_store=beads_store)\n It threads inbox_store + beads_store (those tools DO bind) but NEVER passes goal_enabled -> defaults False -> set_goal is never added to the bound graph.\n\nWHY IT WENT UNNOTICED (registry vs binding mismatch):\n- /api/tools (operator_api/console_handlers.py:_operator_tools_list) DOES pass goal_enabled=bool(cfg.goal_enabled) (=True here), so the Tools tab + docs advertise set_goal as available — but the model never gets it.\n- The /goal CHAT CONTROL MESSAGE still works because it is parsed in server/chat.py (goal_controller.parse_control) BEFORE the graph runs — it does not use the set_goal tool. So goal mode 'appears' functional in the console while the agent-facing tool (ADR 0028: 'the agent owns a plugin-verified goal') is dead. This matters for autonomous/fleet/autopilot operation where the agent must set its own goal without a human typing /goal.\n\nCONFIG STATE confirming the tool SHOULD be bound: graph/config.py:353 goal_enabled defaults True; runtime cfg.goal_enabled=True (set_goal appears in /api/tools). goal_controller IS constructed (server/agent_init.py:250).\n\nFIX: pass goal_enabled into the live build —\n graph/agent.py:661 get_all_tools(knowledge_store, scheduler=scheduler, inbox_store=inbox_store, beads_store=beads_store, goal_enabled=config.goal_enabled)\n(subagent builds at agent.py:328/731 should stay goal-less — subagents are bounded by max_turns and must not self-set goals.) Add a regression test asserting set_goal is in the lead-agent bound toolset when goal_enabled, and add an enable-parity check so /api/tools and the bound graph can't diverge (compute the model's real bound list from the compiled graph rather than re-deriving it).\n\nNOTE: supersedes my earlier inference (in bd-3b6) that the set_goal tool would hit the current_session_id() empty-in-tool-body guard — moot, since set_goal never runs (not bound). bd-3b6's wait finding is unaffected (wait IS bound and DID run).","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-14T19:58:57.252934Z","created_by":"kj","updated_at":"2026-06-14T20:17:10.592873Z","closed_at":"2026-06-14T20:17:10.592465Z","close_reason":"Fixed in PR #1023 (merged cec3babf): create_agent_graph now threads goal_enabled.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0028","goals","regression","tools"]} -{"id":"bd-2f4","title":"Explore repository","description":" way the repository to understand its structure and technologies","status":"in_progress","priority":2,"issue_type":"task","created_at":"2026-06-14T08:43:43.520539Z","created_by":"kj","updated_at":"2026-06-14T08:43:45.052356Z","source_repo":".","compaction_level":0,"original_size":0} -{"id":"bd-2mf","title":"Setup wizard: 'Project path' is cosmetic — make it authoritative","description":"The wizard's 'Project path' is only folded into operator.allowed_dirs (SetupWizard.tsx:264) and NOT persisted as a project_path. The real operator project root is resolved server-side by _resolve_operator_project_root() (server/__init__.py:97 -> PROTOAGENT_PROJECT_DIR env, else repo root), which never reads the field. So typing a path there does not relocate the workspace. Fix: add operator.project_dir config field (graph/config.py dataclass+load, settings_schema Field), have _resolve_operator_project_root read it (env > config > fallback), and persist it from the wizard.","status":"open","priority":1,"issue_type":"bug","created_at":"2026-06-15T04:58:18.104191Z","created_by":"kj","updated_at":"2026-06-15T04:58:18.104191Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["console","setup"]} -{"id":"bd-2pa","title":"Plugin ecosystem: verify + kit-adopt all protoagent-plugin topic repos","description":"AUDITED 2026-06-12 — all 7 topic repos. NONE use plugin-kit.js (so none hit the dead classic-script bug, protoContent#224); all six views hand-roll the protoagent:init handshake (works, partially themed). FIXED (rule 3, PRs): artifact-plugin#1 (hardcoded /api/ data fetch — hub-not-member through the fleet proxy), prototrader-finance#2 (hardcoded kit CSS + api paths), spacetraders-plugin#4 (hardcoded kit CSS). FILED (rule 2, ungated data routes under public /plugins/ prefix — security, surfaced not auto-changed): prototrader-finance#3, spacetraders-plugin#5. CLEAN: doom-plugin + agent-browser-plugin + projectBoard-plugin (slug-aware CSS+data, pre-kit handshake), pm-stack (bundle, no view). REMAINING WORK = kit-JS adoption (dynamic-import pattern per protoAgent#949) across the six view repos, picking up live re-theme + apiFetch for free.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-06-13T00:33:09.357496Z","created_by":"kj","updated_at":"2026-06-13T01:17:33.371034Z","closed_at":"2026-06-13T01:17:33.370509Z","close_reason":"SHIPPED 2026-06-12, all 6 view repos merged + released: kit-JS adoption everywhere (dynamic-import pattern; hand-rolled TMAP/listeners deleted) + FOUR rule-2 regates (prototrader#4 backtest API, spacetraders#6 /state, agent-browser#8 shot/nav [worst: ungated remote browser control], projectBoard#3 operator CRUD — the latter two found mid-flight, the audit's fetch-path check had missed them). Each verified end-to-end against the real served kit in the protoAgent playwright harness pre-merge; bonus fixes: prototrader pos/neg metric color (string-compare bug), spacetraders sparse-server-data crash. Releases: artifact v0.3.0, projectBoard v0.2.0 + agent-browser v0.2.0 (pm-stack pins move via its ADR-0049 verify-and-bump loop). OPEN remainder: agent-browser#9 (dash-proxy gate needs design — iframe can't carry a bearer).","source_repo":".","compaction_level":0,"original_size":0,"labels":["ds","plugins"]} -{"id":"bd-2pp","title":"Chat: paste images from clipboard as attachments","description":"Robustly accept clipboard images (screenshots) on paste — iterate clipboardData.items getAsFile() for image/*, not just clipboardData.files; synthesize a name when missing. Routes through uploadAttachment (native-vision or pipeline).","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T07:53:40.374470Z","created_by":"kj","updated_at":"2026-06-14T08:02:58.822131Z","closed_at":"2026-06-14T08:02:58.821729Z","close_reason":"shipped #1013: clipboard images (items[].getAsFile) → attachment; drag-drop too.","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ui"]} -{"id":"bd-2qy","title":"Non-streaming chat (/api/chat + OpenAI-compat) returns empty 200 on interrupt / wait / scratch-only turns","description":"Found while driving the running agent over /api/chat (model protolabs/fast).\n\nSYMPTOM: certain turns return {\"response\":\"\",\"messages\":[{\"role\":\"assistant\",\"content\":\"\"}]} with HTTP 200 — a silent empty assistant message, no signal to the caller.\n\nReproduced (each its own session_id):\n- ask_human (HITL): 'Use the ask_human tool to ask me my timezone...' -> empty 200. The graph DID interrupt (a follow-up plain message resumed coherently), so the question was swallowed.\n- wait tool (ADR 0053): 'Use the wait tool to pause 20s then report UTC time' -> empty 200, zero acknowledgment the agent yielded/is waiting.\n\nROOT CAUSE: server/chat.py::_chat_langgraph (non-streaming, behind /api/chat and the OpenAI-compatible /v1/chat/completions) uses ainvoke + _last_ai + extract_output with NO handling for the cases its streaming sibling _chat_langgraph_stream covers:\n 1. pending interrupts -> streaming yields ('input_required', {question}); non-streaming returns '' (question lost).\n 2. dropped scratch-only turn -> streaming runs the DROPPED_SCRATCH_KICKER retry; non-streaming has no retry.\n 3. any empty-output turn -> bare empty assistant message, HTTP 200, no fallback text.\n\nIMPACT: OpenAI-compatible consumers (LiteLLM gateway, OpenWebUI) and the /api/chat operator endpoint see an empty completion whenever the agent asks for input, yields via wait, or emits scratch-only. Looks like the model returned nothing.\n\nSUGGESTED FIX: port the streaming path's handling into _chat_langgraph — after ainvoke, check aget_state for pending interrupts and return the question (e.g. 'The agent needs input: '); run the dropped-scratch kicker once; and provide a non-empty fallback for an empty-output turn. RELATED (lower concern, likely by-design): show_component payloads are also silently dropped on the non-streaming path (components are a console-streaming feature).","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-14T19:41:17.348059Z","created_by":"kj","updated_at":"2026-06-14T20:31:47.435676Z","closed_at":"2026-06-14T20:31:47.435247Z","close_reason":"Fixed in PR #1029 (merged): _chat_langgraph surfaces interrupts, runs the kicker, and falls back to tool text.","source_repo":".","compaction_level":0,"original_size":0,"labels":["a2a","chat","robustness"]} -{"id":"bd-2wa","title":"Desktop: first-boot-after-update reconcile hook (version-coherence P2)","description":"When the sidecar version changes (in-app update #918 or manual dmg swap): reconcile detached fleet members from fleet.json + surface plugin re-pin prompts via the #887 check_updates infra. Server-side; prevents in-app updates amplifying the desync class.","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-06-11T08:42:55.743894Z","created_by":"kj","updated_at":"2026-06-11T09:14:16.140420Z","closed_at":"2026-06-11T09:14:16.139927Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["desktop","version-coherence"]} -{"id":"bd-2zb","title":"Settings: field-section regroup for Host config (deferred from ADR 0047 S-D)","description":"Host config renders host-scoped fields in their original schema sections (the 7 box-runtime knobs sit in one flat Fleet group). Purpose-grouped layout was deferred from S-D (#929); noted on #916's closure. Cosmetic — fold into the next settings pass.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-13T00:33:09.170664Z","created_by":"kj","updated_at":"2026-06-14T07:33:06.870511Z","closed_at":"2026-06-14T07:33:06.870091Z","close_reason":"Shipped: regrouped the 7 host box-runtime knobs from one 'Fleet' section into Network/Discovery/Keep-warm (all System category → Host config). UI grouping only; same fields/cascade/save path. settings_schema.py.","source_repo":".","compaction_level":0,"original_size":0,"labels":["console","settings"]} -{"id":"bd-2zh","title":"First-party web-research user-facing skill is unreachable — slash token 'research' collides with the deep-research workflow","description":"Found while inventorying slash commands on the running agent (GET /api/chat/commands).\n\nconfig/skills/web-research/SKILL.md declares 'user_facing: true' and 'slash: research'. But there is also a 'research' deep-research WORKFLOW. Per ADR 0052 dispatch order, workflows/subagents of the same token win and the skill is hidden from the command list (operator_api/console_handlers.py dedup: 'if token in taken: continue'). Net: this first-party user-facing skill ships but can never be invoked via slash, and never appears in the command palette.\n\nThe sibling skill config/skills/release-notes (slash: release-notes) has no collision and works correctly (verified /release-notes end-to-end).\n\nSUGGESTED FIX: give the web-research skill a non-colliding slash token (e.g. 'webresearch' or 'gather'), or rename one side. Optionally: surface a startup warning / dedupe report when a user_facing skill's slash token is shadowed, so shipped-but-unreachable skills are caught at load.","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-06-14T19:41:17.633882Z","created_by":"kj","updated_at":"2026-06-14T20:31:47.711589Z","closed_at":"2026-06-14T20:31:47.711176Z","close_reason":"Fixed in PR #1029 (merged): web-research slash renamed to /web-research + warn-once on shadowed skill slugs.","source_repo":".","compaction_level":0,"original_size":0,"labels":["plugins","skills"]} -{"id":"bd-383","title":"A2A alignment polish (ADR 0051 Slice 3)","description":"Correct the stale cancel/resubscribe docstrings (executor.py says cancel is mark-only; it isn't) + the memory note; agent-card polish (documentation_url, icon_url, explicit top-level protocol_version); cheap realtime bus wins (scheduler.fired, goal.iteration, turn.usage); audit that every outbound protoAgent client sends A2A-Version: 1.0 (missing header defaults to 0.3 → -32009). Push-config TTL + verify push fires on terminal. From the A2A audit.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-14T06:25:38.280543Z","created_by":"kj","updated_at":"2026-06-14T07:41:32.584216Z","closed_at":"2026-06-14T06:33:19.009519Z","close_reason":"Shipped: delegate A2A-Version fix, cancel/resubscribe docstring+memory correction, card documentation_url/icon_url, turn.usage + goal.iteration bus events. Deferred scheduler.fired/push-TTL noted.","source_repo":".","compaction_level":0,"original_size":0,"labels":["a2a","adr-0051"],"comments":[{"id":2,"issue_id":"bd-383","author":"Josh Mabry","text":"Push-on-terminal VERIFIED 2026-06-14: not a gap. PushNotificationEvent is an alias = Task|TaskStatusUpdateEvent|TaskArtifactUpdateEvent; active_task._update_task_state fires push_sender.send_notification when isinstance(event, that union) — and updater.complete() emits a TaskStatusUpdateEvent → push fires on terminal (and every status/artifact update) for a registered webhook. Runtime-confirmed isinstance True. No code change needed.","created_at":"2026-06-14T07:41:32Z"}]} -{"id":"bd-39m","title":"Chat UX refresh: migrate ChatSurface to @protolabsai/ui/ai","description":"Full chat-UX refresh (user-chosen: upstream DS hooks then migrate). Adopt the DS /ai module in ChatSurface: Conversation (auto-scroll), Message, MessageActions (copy/regenerate), TypingIndicator, and PromptInput with attachments + slash menu via the new extension hooks. DEPENDS ON protoContent PR #232 (PromptInput inputRef/onKeyDown/onPaste/overlay) being released (→ @protolabsai/ui 0.33, we're on 0.30). Then: bump ui, migrate composer (slash via onKeyDown+overlay, attachments via onAttach/attachments), migrate thread. File upload (bd-3dh) rides on top: composer collects files → A2A file parts → backend native-multimodal-or-pipeline. Reasoning/CoT display = separate (we strip server-side today).","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T04:22:52.268046Z","created_by":"kj","updated_at":"2026-06-15T02:34:31.948362Z","closed_at":"2026-06-15T02:34:31.947961Z","close_reason":"Done — ChatSurface fully migrated to @protolabsai/ui/ai (verified during testing pass): Conversation (auto-scroll), Message (+streaming), MessageActions/MessageAction (copy/regenerate), PromptInput (attachments + slash menu), and Reasoning (CoT display — bd-39m had it as 'separate/stripped', now adopted) + tool-card module. ui bumped 0.30→0.35 (the 0.33 PromptInput blocker is moot). File upload (bd-3dh) shipped. Literal TypingIndicator unused but its intent is covered by Message's streaming prop + Loader2 spinner. Migration complete; nothing actionable remains.","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ds","ui"]} -{"id":"bd-3b6","title":"wait same-session resume is broken: context_id never stamped (tool body reads current_session_id() empty) — resume fires into Activity thread, not the chat","description":"DEEP-DIVE on ADR 0053 (wait yield-and-resume), driving the running agent over /a2a. The headline promise — 'a wait issued inside a chat resumes in THAT chat's thread, agent wakes with history intact' — fails. The resume fires into the durable Activity thread instead, with none of the chat context.\n\nEVIDENCE CHAIN (live, model protolabs/fast):\n1. Sent SendMessage to /a2a with contextId=wd1: 'call wait 30s, then=...RESUMED-WD1...'. Task -> TASK_STATE_COMPLETED, history shows wait called with {seconds:30, then:...} (yield middleware worked).\n2. Scheduler jobs.db immediately after: a new one-shot job, correct next_fire (+30s), correct 'then' prompt — but context_id = NULL (typeof null, quote NULL). Expected 'wd1'.\n3. audit.jsonl: 19:44:42 sid='wd1' ev=wait (so the turn's session WAS known as wd1).\n4. audit.jsonl: 19:45:16 sid='system:activity' ev=current_time (the resume fired — ran the 'then' — but in the ACTIVITY thread, not wd1).\n5. job gone from DB after firing (one-shot). => resume ran detached in Activity, no wd1 history.\n\nROOT CAUSE: tools/lg_tools.py:697 ctx = tracing.current_session_id() or None returns '' inside the WAIT TOOL BODY, so ctx=None -> add_job(context_id=None) -> scheduler/local.py _fire uses 'job.context_id or ACTIVITY_CONTEXT' -> Activity.\nKEY: tracing.current_session_id() is reliably set during the turn — graph/middleware/audit.py reads the SAME contextvar and correctly logged sid='wd1' for this very wait call. But LangGraph runs MIDDLEWARE hooks and TOOL BODIES in different execution contexts: the trace_session contextvar (observability/tracing.py:148, set before yield) is visible to middleware (wrap_tool_call) but NOT to the tool function body. (Verified contextvar propagation through asynccontextmanager + async-generator + create_task + to_thread all work in isolation, so this is a LangGraph tool-execution-context boundary, not a generic CPython issue.)\n\nBLAST RADIUS — other current_session_id() callers in TOOL BODIES:\n- tools/lg_tools.py:796 set_goal — same pattern, but it GUARDS: 'if not session_id: return \"No active session — set_goal can only run during a turn.\"'. Inference: the set_goal TOOL would always refuse mid-turn. UNVERIFIED at runtime (the model claimed set_goal was not in its bound toolset and wouldn't invoke it — itself worth checking: /api/tools lists set_goal but the model says it's unavailable; possible registry-vs-binding mismatch).\n- Middleware callers (audit.py:35/88, memory.py:96) and graph/agent.py:277/464 run in contexts where the var IS set — not affected the same way.\n\nWHY EXISTING TEST DOESN'T CATCH IT: tests/test_wait_yield.py:125 monkeypatches tracing.current_session_id to return 'chat-abc', so it verifies the stamping LOGIC in isolation but masks the real-runtime emptiness. False confidence.\n\nSUGGESTED FIX: source the session id from the graph STATE, not the tracing contextvar, inside tools. graph/state.py:22 already declares 'session_id: NotRequired[str]' and the graph input sets it ({'messages':..., 'session_id': session_id}). Use LangGraph InjectedState in the wait (and set_goal) tools to read state['session_id']; keep current_session_id() as a fallback. Add an INTEGRATION test that drives a real turn (not a monkeypatch) and asserts the job's context_id == the turn's session.\n\nRELATED: bd-k02 (live UI surfacing of the resumed turn) is moot until the resume lands in the right thread. Separate from bd-2qy (non-streaming empty-200).","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-14T19:51:04.770082Z","created_by":"kj","updated_at":"2026-06-14T20:17:10.871136Z","closed_at":"2026-06-14T20:17:10.870705Z","close_reason":"Fixed in PR #1023 (merged cec3babf): state_schema=ProtoAgentState + InjectedState; integration-tested.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0053","robustness","scheduler","wait"]} -{"id":"bd-3dh","title":"Chat: file upload — native multimodal pass-through or pipeline fallback","description":"PARKED 2026-06-14 (test extraction pipeline first). Add file upload to the chat composer: if the active model supports native image/video, send it as an A2A image/file part; else 'process via our pipeline' (ingestion engine extract). RESEARCH DONE: (1) @protolabsai/ui 0.31 already has /ai PromptInput composer attachments (PromptAttachment type, AttachmentChip, onAttach/attachments/onRemoveAttachment — presentation-only, host owns picker); we're on 0.30 → bump to get them. (2) Our composer is hand-rolled (slash-menu + HITL + auto-grow), NOT the DS PromptInput → either extend ours or migrate. (3) A2A parts support file/mimeType on the wire, but backend has NO multimodal path (nothing builds LangChain image content) and NO model-modality config. (4) ingestion/extract_bytes already = the pipeline. OPEN FORKS (unanswered): fallback semantics (inline-this-turn vs KB-ingest vs both); composer (extend vs DS migrate); capability detection (config flag vs map vs probe). Current gateway chat model likely not vision-capable → pipeline path primary.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T02:55:32.213807Z","created_by":"kj","updated_at":"2026-06-14T05:47:44.857617Z","closed_at":"2026-06-14T05:47:44.856944Z","close_reason":"shipped 2026-06-14: tiered session-scoped chat attachments — backend #999 (POST /api/knowledge/attach + delete_by_namespace cleanup) + composer UI #1002 (attach button / paste / drag-drop). Native multimodal still parked (gateway chat model not vision-capable); pipeline path live.","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ingestion","knowledge"]} -{"id":"bd-3dz","title":"RAG: batched embedding for add_document","description":"Deferred from the 2026-06 RAG initiative. add_document now embeds each chunk with a serial gateway call (N chunks = N round-trips); contextual enrichment (#988) adds another serial aux call per chunk. Batch the embeds (one /embeddings request with multiple inputs) to cut ingest latency, especially for the new audio/video transcripts (#992) which can be many chunks.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-14T02:22:03.425762Z","created_by":"kj","updated_at":"2026-06-14T03:11:19.784768Z","closed_at":"2026-06-14T03:11:19.784086Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["knowledge","rag"]} -{"id":"bd-3g4","title":"RAG: long-doc/chunking case in retrieval-eval gold set","description":"Deferred from the 2026-06 RAG initiative. evals/retrieval_gold.yaml has no long-document case, so the lift from #987 (chunking) + #988 (contextual enrichment) is asserted but not QUANTIFIED. Add a long-doc gold case (ingest via add_document, query specific passages) to prove the lift numerically. Offline (bow embedder) so no gateway needed.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-14T02:22:03.218530Z","created_by":"kj","updated_at":"2026-06-14T04:09:24.226445Z","closed_at":"2026-06-14T04:09:24.226012Z","close_reason":"parked 2026-06-14: deferred with the reranker it was meant to justify; revisit if/when we invest in retrieval-quality measurement again.","source_repo":".","compaction_level":0,"original_size":0,"labels":["evals","knowledge","rag"]} -{"id":"bd-3j1","title":"RAG: cross-encoder reranker (retrieve-N → rerank → top-k)","description":"Deferred from the 2026-06 RAG initiative. protoLab bake-off showed +17% from a cross-encoder rerank (retrieve ~40 → rerank → top-k). Needs an ADR 0031 amendment first — 0031 explicitly DECLINED a rerank seam. No reranker on the gateway (probed), so it'd be a local cross-encoder. Measure with the #986 retrieval-eval harness.","status":"closed","priority":3,"issue_type":"feature","created_at":"2026-06-14T02:22:02.994895Z","created_by":"kj","updated_at":"2026-06-14T04:09:23.994901Z","closed_at":"2026-06-14T04:09:23.994158Z","close_reason":"parked 2026-06-14: premature — recall already strong post-enrichment, no gateway reranker (needs local cross-encoder + ADR 0031 amend + query-time latency). Revisit only if eval shows a ranking problem at scale.","source_repo":".","compaction_level":0,"original_size":0,"labels":["knowledge","rag"]} -{"id":"bd-3r1","title":"artifact-plugin tightening — follow-ups after the view-mount fix (v0.4.0)","description":"artifact-plugin take-over follow-ups. DONE: (a) P0 view-mount fix + tests + CI (#3, v0.4.0); (b) CDN SRI — react/mermaid/babel/mermaid pinned with integrity+crossorigin, version+hash move together via a CDN map, render-verified in harness (#4, v0.4.1). REMAINING = JUDGMENT CALLS (need product/owner decision, not clear-cut): (1) OFFLINE via vendoring — bundle react(~130KB)+react-dom+babel-standalone(~3MB!)+mermaid(~2.8MB) as plugin-served assets = ~6MB in the repo, removes CDN dependency + works offline (desktop). Heavy; only if offline artifacts matter. (2) CSP — the artifact sandbox runs ARBITRARY code by design (the feature), so a strict CSP (esp. connect-src to block exfil/phone-home) WOULD break legitimate artifacts that fetch APIs / show remote images. Real product decision for ADR 0038, not a unilateral tighten. The sandbox (allow-scripts, no same-origin) is the actual security boundary and is correct/minimal.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-13T04:05:01.063580Z","created_by":"kj","updated_at":"2026-06-13T05:00:45.853629Z","closed_at":"2026-06-13T05:00:45.853186Z","close_reason":"DONE 2026-06-13. SRI (#4, v0.4.1) + OFFLINE VENDORING (#5, v0.5.0 — react/react-dom/babel/mermaid ~6MB served same-origin from /plugins/artifact/vendor, every kind renders with 0 outbound network, harness-proven with cdnjs aborted; crossorigin+ACAO load-bearing because the sandbox is an opaque origin). CSP = deliberate NON-GOAL per owner (artifacts are arbitrary code by design; the no-same-origin sandbox is the boundary). artifact-plugin take-over complete: v0.3.0 → v0.5.0, P0 broken-panel fix + tests + CI + size cap + SRI + offline.","source_repo":".","compaction_level":0,"original_size":0,"labels":["artifact","plugins"]} -{"id":"bd-3up","title":"React/Tauri operator console","description":"Replace the Gradio operator UI with a React console using Ava-style multi-chat, manual subagent launch, notes, beads task rendering, and eventual Tauri packaging.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-05-29T06:31:39.130436Z","created_by":"kj","updated_at":"2026-05-29T07:41:55.271311Z","closed_at":"2026-05-29T07:41:55.270927Z","close_reason":"React/Tauri operator console epic complete: API contracts, Vite React shell, Orbis-style setup wizard, Ava-style multi-chat pool, manual subagent launcher, notes workspace tabs, beads renderer, and Tauri desktop wrapper are implemented and validated.","source_repo":".","compaction_level":0,"original_size":0,"labels":["react","tauri","ui"]} -{"id":"bd-3up.1","title":"Define React UI backend contracts","description":"Add tested JSON contracts for runtime status, subagent listing/launch, notes workspace load/save, and br-backed beads operations before the React UI depends on them.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-29T06:31:39.245893Z","created_by":"kj","updated_at":"2026-05-29T06:46:31.728893Z","closed_at":"2026-05-29T06:46:31.728498Z","close_reason":"Operator-console backend contracts implemented: runtime status, subagent discovery/manual launch, notes workspace persistence, and br-backed beads APIs with focused tests.","source_repo":".","compaction_level":0,"original_size":0,"labels":["api","ui"],"dependencies":[{"issue_id":"bd-3up.1","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.245893Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.2","title":"Scaffold React/Vite operator app","description":"Create apps/web with React, TypeScript, routing/shell slots, API client, protoContent-derived proto brand theme tokens, and FastAPI static serving under /app while leaving Gradio available.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-29T06:31:39.366086Z","created_by":"kj","updated_at":"2026-05-29T07:01:22.254270Z","closed_at":"2026-05-29T07:01:22.253862Z","close_reason":"React/Vite operator app scaffold implemented under apps/web with protoContent-derived theme tokens, API client, shell surfaces, FastAPI /app static serving, and focused mount tests.","source_repo":".","compaction_level":0,"original_size":0,"labels":["react","ui"],"dependencies":[{"issue_id":"bd-3up.2","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.366086Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.3","title":"Port Orbis-style setup wizard","description":"Adapt the Orbis first-run wizard to protoAgent: identity, model gateway, SOUL preset/editor, tools/subagents, workspace paths, optional beads init, and final setup POST.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-29T06:31:39.493230Z","created_by":"kj","updated_at":"2026-05-29T07:08:39.620278Z","closed_at":"2026-05-29T07:08:39.619865Z","close_reason":"Orbis-style setup wizard ported into the React app: setup-status/config hydration, identity, model probe, SOUL preset/editor, middleware/subagent defaults, workspace path, optional beads init, and final /api/config/setup submission.","source_repo":".","compaction_level":0,"original_size":0,"labels":["onboarding","ui"],"dependencies":[{"issue_id":"bd-3up.3","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.493230Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.4","title":"Build Ava-style multi-chat session pool","description":"Port the persisted chat store and mounted session pool so up to five hidden sessions can keep streaming through A2A while the user switches tabs.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-29T06:31:39.611406Z","created_by":"kj","updated_at":"2026-05-29T07:17:00.605592Z","closed_at":"2026-05-29T07:17:00.605204Z","close_reason":"Ava-style multi-chat pool implemented: persisted localStorage sessions capped at 50, active mounted session pool capped at 5, hidden sessions remain mounted, per-session streaming status, A2A message/stream client, task cancel stop control, and responsive session tabs.","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ui"],"dependencies":[{"issue_id":"bd-3up.4","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.611406Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.5","title":"Expose manual subagent launcher","description":"Refactor subagent execution behind a service and expose GET /api/subagents plus POST /api/subagents/run and /batch for user-launched researcher jobs.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-29T06:31:39.736120Z","created_by":"kj","updated_at":"2026-05-29T07:19:42.202717Z","closed_at":"2026-05-29T07:19:42.202161Z","close_reason":"Manual subagent launcher complete: backend service/API contracts for listing/run/batch are wired, React panel supports single and batch launches, task rows select subagent type/description/prompt, and results render in the operator console.","source_repo":".","compaction_level":0,"original_size":0,"labels":["subagents","ui"],"dependencies":[{"issue_id":"bd-3up.5","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.736120Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.6","title":"Port notes workspace surface","description":"Port the ProtoMaker notes tab model with debounced backend save and per-tab agent read/write permissions for chat/subagent context injection.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-29T06:31:39.862245Z","created_by":"kj","updated_at":"2026-05-29T07:23:20.511553Z","closed_at":"2026-05-29T07:23:20.511130Z","close_reason":"Notes workspace surface ported: multi-tab notes with inline rename, protected last-tab delete, per-tab agent read/write toggles, dirty indicator, manual save, and debounced backend persistence.","source_repo":".","compaction_level":0,"original_size":0,"labels":["notes","ui"],"dependencies":[{"issue_id":"bd-3up.6","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.862245Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.7","title":"Build beads task list renderer","description":"Add a Python br --json service and React task list renderer with init empty state, create row, grouped status table, and start/close/delete actions.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-29T06:31:39.990657Z","created_by":"kj","updated_at":"2026-05-29T07:31:18.346916Z","closed_at":"2026-05-29T07:31:18.346500Z","close_reason":"Beads renderer added: typed create row, grouped status sections, start/open/close/delete actions, init/not-checked empty states, API client coverage for update/close/delete, and route/service tests.","source_repo":".","compaction_level":0,"original_size":0,"labels":["beads","ui"],"dependencies":[{"issue_id":"bd-3up.7","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:39.990657Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3up.8","title":"Add Tauri desktop wrapper","description":"After the browser React app is usable, add a Tauri v2 wrapper with tray, global hotkey, hide-on-close, and connect-to-local-server mode before sidecar bundling.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-05-29T06:31:40.114759Z","created_by":"kj","updated_at":"2026-05-29T07:41:35.883680Z","closed_at":"2026-05-29T07:41:35.883258Z","close_reason":"Tauri desktop wrapper added: @protoagent/desktop workspace, Tauri v2 shell, proto icon bundle assets, tray show/hide/quit menu, Cmd+Shift+P/Super+Shift+P toggle shortcut, hide-on-close behavior, local-server API routing for packaged UI, and validated macOS app/DMG build.","source_repo":".","compaction_level":0,"original_size":0,"labels":["tauri","ui"],"dependencies":[{"issue_id":"bd-3up.8","depends_on_id":"bd-3up","type":"parent-child","created_at":"2026-05-29T06:31:40.114759Z","created_by":"kj","metadata":"{}","thread_id":""}]} -{"id":"bd-3uz","title":"Agent: wait/yield tool to stop busy-polling (recursion-limit burn)","description":"Agent busy-polled st_ship to wait for arrival → hit GRAPH_RECURSION_LIMIT(200). Add a core wait(seconds,then) tool that schedules a scheduler resume + a WaitYieldMiddleware that ends the turn. ADR 0053.","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-06-14T08:37:21.015322Z","created_by":"kj","updated_at":"2026-06-14T08:37:29.963475Z","closed_at":"2026-06-14T08:37:29.963049Z","close_reason":"shipped #1015 (ADR 0053): wait(seconds,then) core tool + WaitYieldMiddleware ends the turn → scheduler resumes in Activity thread. Lead-only. 1982 backend pass.","source_repo":".","compaction_level":0,"original_size":0,"labels":["agent","core"]} -{"id":"bd-3v0","title":"Background subagent (ADR 0050) origin_session captured empty — completions never drain back to the spawning chat","description":"Third instance of the contextvar-empty-in-tool-body class (see #1023 / [[tool-session-injection]]); NOT fixed by #1023 (that touched wait + set_goal only).\n\nVERIFIED empirically: drove a real create_agent graph whose model calls task(run_in_background=True); a fake background manager recorded origin_session='' (expected the turn's session 'sess-BG').\n\nROOT CAUSE: graph/agent.py:465 — task tool's _spawn_bg does origin_session=tracing.current_session_id(), which reads EMPTY in a tool body under LangGraph (only middleware sees the contextvar). So background.store.create(origin_session='') and the terminal hook can only map back to ''.\n\nIMPACT: ADR 0050 promises 'you will be notified of the result automatically on a later turn', but server/chat.py::_drain_background_messages(session_id) calls store.drain_pending(session_id) which matches origin_session==session_id. With origin_session='' and real sessions having non-empty ids, the completion is ORPHANED — the spawning chat never receives the , the agent never learns the task finished. The job still runs + stores its result; it just never gets delivered.\n\nFIX: same as #1023 — read the session from injected graph state (state_schema=ProtoAgentState is now wired) instead of the contextvar. Add state: Annotated[Any, InjectedState] to the task tool and pass the resolved session into _spawn_bg (reuse tools.lg_tools._session_id_from). Check task_batch's background path too. Keep current_session_id() as off-graph fallback.\n\nALSO (cosmetic, low pri): graph/agent.py:278 source_session_id=current_session_id() on the emitted SkillV1Artifact reads empty for the same reason — skill still emits/persists, only the source attribution is blank.\n\nREGRESSION TEST: drive a real graph turn (fake model emitting task(run_in_background=True)); assert the spawn's origin_session == the turn's session_id.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-14T20:39:21.148435Z","created_by":"kj","updated_at":"2026-06-15T02:07:49.297430Z","closed_at":"2026-06-15T02:07:49.297016Z","close_reason":"Fixed + merged in PR #1030 (95b0114) — stale in_progress; closing during standup.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0050","background","robustness","tools"]} -{"id":"bd-3vp","title":"Scheduler fires overdue jobs before Uvicorn is accepting connections → noisy connection-refused on startup catch-up","description":"On scheduler start, _recover_missed_fires + the first poll can fire overdue one-shot jobs by POSTing to /a2a BEFORE 'Application startup complete' — the POST gets a connection error (httpx/httpcore), logged as 'fire exception' + 'one-shot fire failed; leaving for retry'. It self-heals (the poll loop retries the one-shot next tick and it fires once /a2a is reachable), so no job is lost — but it error-logs a scary traceback for an expected startup-ordering condition. Fix: gate the first fire until the HTTP server is ready, or treat a connection error to our own /a2a during startup as a quiet retry (debug, not error). Lower priority — self-healing.","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-06-15T01:27:04.917415Z","created_by":"kj","updated_at":"2026-06-15T01:49:10.275705Z","closed_at":"2026-06-15T01:49:10.275302Z","close_reason":"Fixed in PR #1042 (merged 829b79b0): _fire logs ConnectError concisely at INFO, not an ERROR traceback.","source_repo":".","compaction_level":0,"original_size":0,"labels":["robustness","scheduler"]} -{"id":"bd-67j","title":"Tools tab (/api/tools) under-reports the agent's real tools — omits task/task_batch, filesystem, execute_code, search_tools, background-control","description":"VERIFIED live: GET /api/tools lists 62 tools but OMITS task, task_batch, filesystem tools (read_file/write_file/list_projects/run_command/...), execute_code, search_tools, and the background-control tools — all of which create_agent_graph binds to the model. filesystem_enabled defaults True, so the agent CAN read/write files in allowed dirs, but the operator's Tools tab never shows it (a transparency gap; execute_code is off by default so its absence is currently correct).\n\nROOT CAUSE: operator_api/console_handlers.py::_operator_tools_list re-derives the inventory from get_all_tools(...) + STATE.plugin_tools + STATE.mcp_tools. But get_all_tools is the SHARED lead+subagent base; create_agent_graph adds the lead-only/config-gated tools SEPARATELY (task/task_batch via _build_task_tools, fs via build_fs_tools, execute_code, deferred search_tools, background-control). So the Tools tab structurally cannot see them. Same registry-vs-binding CLASS as bd-2aa, but under-reporting instead of over-reporting.\n\nDURABLE FIX: make the Tools tab read the ACTUAL bound tools. create_agent_graph stamps its final assembled tool list onto the compiled graph (e.g. agent.protoagent_bound_tools = all_tools); _operator_tools_list enumerates that, deriving source by cross-referencing STATE.plugin_tools / STATE.mcp_tools name sets, with the get_all_tools re-derivation kept only as a pre-setup fallback. This permanently kills BOTH over-report (bd-2aa: set_goal shown but unbound) and under-report drift — the Tools tab becomes exactly what the model can call.\n\nREGRESSION TEST: build a real create_agent_graph; assert _operator_tools_list (pointed at it) lists task + the fs tools (filesystem on) and that the set equals the compiled graph's bound tools.","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-06-14T20:45:37.689159Z","created_by":"kj","updated_at":"2026-06-14T21:05:47.017295Z","closed_at":"2026-06-14T21:05:47.016862Z","close_reason":"Fixed in PR #1031 (merged): Tools tab reads graph.bound_tools — single source.","source_repo":".","compaction_level":0,"original_size":0,"labels":["console","observability","tools"]} -{"id":"bd-873","title":"Chat: paste large text as a removable attachment pill","description":"Pasting text over a threshold (chars or lines) should become a removable attachment pill (routed through the attach pipeline → tiered inline/indexed) instead of dumping into the input. Threshold ~1500 chars / 20 lines.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T07:53:40.133476Z","created_by":"kj","updated_at":"2026-06-14T08:02:58.575760Z","closed_at":"2026-06-14T08:02:58.575309Z","close_reason":"shipped #1013: large text paste → removable attachment pill (tiered).","source_repo":".","compaction_level":0,"original_size":0,"labels":["chat","ui"]} -{"id":"bd-bq4","title":"set_goal accepted an unknown verifier name → unsatisfiable goal that loops to the cap","description":"Found driving the live agent: the set_goal TOOL accepted check='manual' (not a registered plugin verifier) and CREATED the goal anyway. The goal can never pass — run_verifier returns 'unknown plugin verifier' each tick — so it spins toward the iteration cap and ends 'unachievable'. set_goal_safe only checked the verifier TYPE (plugin) and that 'check' was non-empty, never that the name resolves.\n\nFIX (#): the set_goal tool now validates 'check' against graph.goals.verifiers.plugin_verifier_names() (new accessor) BEFORE creating the goal, returning 'Error: unknown plugin verifier . Available verifiers: ...' so the agent can pick a real one. Validation lives in the tool (the agent-facing path) not set_goal_safe, so the operator /goal + REST/plugin paths keep their existing graceful runtime fallback and the controller test suite is unaffected. Regression test added (test_set_goal_rejects_an_unknown_verifier).","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-15T01:17:49.923715Z","created_by":"kj","updated_at":"2026-06-15T01:20:24.313961Z","closed_at":"2026-06-15T01:20:24.313558Z","close_reason":"Fixed in PR #1040 (merged): set_goal validates check against plugin_verifier_names() and rejects unknown.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0028","goals"]} -{"id":"bd-g9c","title":"Background subagents Phase 2: reactivity / idle-wake","description":"ADR 0050 Phase 2. Route a background-job completion into an inbox 'now' item so the existing inbox→Activity-turn fire wakes the agent autonomously — the agent RESPONDS unprompted to the result instead of only learning on the originating session's next turn. Also publish background.{started,progress} on the bus. Builds on the shipped Phase 1 (#996).","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T04:56:04.586053Z","created_by":"kj","updated_at":"2026-06-14T05:17:25.822287Z","closed_at":"2026-06-14T05:17:25.821880Z","close_reason":"Shipped: autonomous idle-wake — completion fires an Activity turn via a now-inbox item (storm-guarded), gated by BACKGROUND_WAKE.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0050","background-subagents"]} -{"id":"bd-hbf","title":"Setup wizard: model field should auto-populate the gateway dropdown","description":"Model field has a datalist + manual 'Probe' button (SetupWizard.tsx:442). Auto-probe on entering the model step when api_base+key are present so the model dropdown is populated without a manual click (matching the settings/composer model pickers).","status":"open","priority":2,"issue_type":"task","created_at":"2026-06-15T04:58:18.381241Z","created_by":"kj","updated_at":"2026-06-15T04:58:18.381241Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["console","setup"]} -{"id":"bd-jip","title":"Standardize .beads/ gitignore — allowlist form","description":"Root .gitignore already denies beads.db/.br_history/last-touched, but daemon.log/metadata.json/.local_version are still loose. Consolidate into a self-contained allowlist .beads/.gitignore so the rules travel with the dir and cover all runtime files.\n\nStandard (matches protoPen PR #73 + upstream pattern in steveyegge/beads#919): replace .beads/.gitignore with the allowlist form:\n\n *\n !issues.jsonl\n !config.yaml\n !routes.jsonl\n !.gitignore\n\nCommitted = shared source of truth (issues.jsonl, config.yaml, routes.jsonl). Ignored = local/runtime (beads.db*, *.lock, .br_history/, daemon.*, metadata.json, .local_version, last-touched, *.tmp). Robust: new volatile files br adds are ignored automatically, no .gitignore churn. Verify with git check-ignore after.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-05-30T09:32:41.005781Z","created_by":"kj","updated_at":"2026-06-02T19:03:49.553416Z","closed_at":"2026-06-02T19:03:49.552714Z","close_reason":"Replaced .beads/.gitignore with the allowlist form; removed redundant .beads/* rules from root .gitignore. Verified with git check-ignore.","source_repo":".","compaction_level":0,"original_size":0,"labels":["beads","chore"]} -{"id":"bd-jus","title":"Inbox: a fired 'now' item is never marked delivered → lingers pending + check_inbox re-surfaces already-handled notifications","description":"Found probing the inbox on the live agent. 5 'now'-priority items (all source=background — ADR 0050 background-completion notifications) sit pending with delivered_at=None, spanning ~20h. They already FIRED their Activity turns (70 system:activity turns ran; check_inbox's own docstring says 'now items already fired a turn'), but _operator_inbox_add (operator_api/console_handlers.py) fires a now-item and never marks it delivered.\n\nIMPACT: every fired now-item lingers as pending forever. check_inbox(priority_floor='next' = now+next, the DEFAULT) lists + mark_delivered's them — so the agent's first check_inbox dumps a backlog of stale, already-handled background-completion notifications and may re-act on them. Inbox pending count also grows unbounded with handled items.\n\nFIX: in _operator_inbox_add, after a SUCCESSFUL fire, mark the item delivered (it was delivered to the agent via the fired turn). Leave it pending only when the fire FAILED, so check_inbox remains the fallback delivery for an unfired now-item. (Existing 5 will self-clear on the next check_inbox; the real data isn't lost — delivered items are still viewable with include_delivered.)","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-06-15T02:00:27.267447Z","created_by":"kj","updated_at":"2026-06-15T02:04:46.222559Z","closed_at":"2026-06-15T02:04:46.222169Z","close_reason":"Fixed in PR #1044 (merged f65bd2fd): _operator_inbox_add marks a fired now-item delivered.","source_repo":".","compaction_level":0,"original_size":0,"labels":["adr-0003","adr-0050","inbox"]} -{"id":"bd-k02","title":"wait same-session resume: live UI surfacing in the chat tab (Slice 2)","description":"ADR 0053 follow-up. Today wait() schedules a resume that fires in the Activity thread (scheduler/local.py::_fire hardcodes contextId=ACTIVITY_CONTEXT). Goal: when wait() is called inside a chat session, the continuation should land in THAT chat tab.\n\nThis is TWO problems (research-confirmed):\nA) AGENT context continuity — route the resumed turn into the originating checkpointer thread so the agent resumes with the conversation history.\nB) USER visibility — make the resumed turn's output appear in the browser chat transcript. The browser chat is display-only + localStorage; it does NOT pull server-side thread messages, and only streams turns IT initiated. So a server-fired turn won't show without an explicit push. The ONLY existing push-into-a-session mechanism is ADR 0050's background.completed -> BackgroundWatch -> appendSystem.\n\nKEY FACTS (file:line):\n- Frontend sets message.contextId = chat session.id (apps/web/src/lib/api.ts:1033 streamChat).\n- Backend derives thread_id via server/chat.py::_resolve_thread_id -> \"a2a:\" (~line 81), used in the turn config (~line 760).\n- Executor passes context.context_id into the stream (a2a_impl/executor.py ~221/333).\n- Tools currently CANNOT read the turn contextId; need to inject it (RunnableConfig param / ensure_config, or stash session_id in config[\"configurable\"] at chat.py:760 and read via an injected config arg on the wait @tool).\n- Job dataclass: scheduler/interface.py:16. SQLite schema: scheduler/local.py:173. add_job ~264, _fire ~516, _row_to_job ~598.\n- background.completed publish: server/a2a.py ~425; consumer apps/web/src/app/BackgroundWatch.tsx:72 (appendSystem -> chat store).\n\nDESIGN (sliced):\nSlice 1 (backend continuity):\n- Add Job.context_id: str|None=None (interface.py).\n- Migrate SQLite: ALTER TABLE jobs ADD COLUMN context_id TEXT (lazy/defensive in _init_db; existing DBs OK); include in INSERT + _row_to_job (guard \"context_id\" in keys).\n- add_job(..., context_id=None).\n- _fire: contextId = job.context_id or ACTIVITY_CONTEXT.\n- wait tool: capture current contextId. Put session_id into the turn config[\"configurable\"][\"a2a_context_id\"] in chat.py (~760); wait reads it via an injected RunnableConfig arg (langchain InjectedToolArg/config param) and passes context_id to add_job. Guard: skip carrying it when contextId == ACTIVITY_CONTEXT.\nResult: agent resumes with continuous history in the chat thread.\n\nSlice 2 (UI surfacing — reuse ADR 0050 pattern):\n- Hook the scheduled resume's TERMINAL outcome (mirror background jobs' terminal hook / server/a2a.py) -> publish a per-session bus event (e.g. wait.resumed or reuse background.completed shape) carrying {origin_session: job.context_id, result, job_id}.\n- Frontend: a watcher (mirror BackgroundWatch) listens and appends the result to that session. DECISION: append as a normal ASSISTANT message (natural continuation), not a system card (the system-card path is for notifications; we just restyled it in #1016).\n- Limitation (document): final-result-only, not live-streamed (server-initiated turn isn't streamed to the browser). Matches the background-completion UX.\n\nEdge cases:\n- Origin tab closed at fire time: append returns false; turn still ran server-side. Toast \"open the chat to read it\" (background pattern). Reopen gap (localStorage won't have it) = separate \"server-side session history sync\" problem (Slice 3, optional/bigger).\n- Chained waits: each resume re-routes to the same context + pushes its result. Works.\n- Activity-origin waits: context_id None -> ACTIVITY_CONTEXT (unchanged).\n\nSlice 3 (optional, larger): live-stream the resumed turn into the chat tab, and/or server-side per-session history so closed/reopened tabs backfill.\n\nNON-GOAL: live streaming of server-initiated turns into chat (Slice 3). Slice 1+2 deliver the feature (continuity + visible-on-completion).","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-14T08:55:48.919304Z","created_by":"kj","updated_at":"2026-06-15T02:24:43.279317Z","closed_at":"2026-06-15T02:24:43.278918Z","close_reason":"Fixed + merged in PR #1046 (4c31305b): chat.resumed event + ChatResumeWatch surface the resumed turn live.","source_repo":".","compaction_level":0,"original_size":0,"labels":["agent","chat","scheduler"]} -{"id":"bd-nfb","title":"RAG: parallelize per-chunk contextual enrichment on ingest","description":"Follow-up to #993 (batched embeddings). Contextual enrichment (#988) still makes N SERIAL aux-LLM calls per document (one doc+chunk prompt per chunk) — this now dominates ingest latency (a 26-chunk enriched doc ~2min even with batched embeds). Can't collapse to one request like embeddings, but the calls are independent → run them concurrently (thread pool, bounded) in _chunk_and_enrich. Keep the first-failure-disables-rest degrade. Measure with a large doc.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-06-14T03:21:22.402961Z","created_by":"kj","updated_at":"2026-06-14T03:28:13.990943Z","closed_at":"2026-06-14T03:28:13.990554Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["knowledge","perf","rag"]} -{"id":"bd-ooe","title":"Adopt DS SideNav in SettingsSurface — retire the interim vertical-nav","description":"protoContent shipped SideNav (PR #227, closes protoContent#225). Swap the app-local interim for the real component once a @protolabsai/ui release carrying SideNav is published + bumped here.\n\nSCOPE:\n- SettingsSurface.tsx: replace the hand-rolled SectionNav (