Skip to content

fix(classifier): FIX-196..198 — classifier determinism and coder route cleanup#10

Open
ikeniborn wants to merge 109 commits intobitgn:mainfrom
ikeniborn:worktree-agent-aaaf10e2
Open

fix(classifier): FIX-196..198 — classifier determinism and coder route cleanup#10
ikeniborn wants to merge 109 commits intobitgn:mainfrom
ikeniborn:worktree-agent-aaaf10e2

Conversation

@ikeniborn
Copy link
Copy Markdown

Summary

  • FIX-196: Fix seed documentation in models.json — classifier profile updated from seed=0 (random in Ollama) to seed=1 for actual determinism; rationale comment corrected accordingly
  • FIX-197: Forward seed parameter to OpenRouter tier in call_llm_raw() — extracted from cfg.ollama_options and passed as create_kwargs["seed"] for cross-tier deterministic sampling; Anthropic SDK lacks seed support (documented with comment)
  • FIX-198: Remove TASK_CODER from _VALID_TYPES and _PLAINTEXT_FALLBACK in classifier.py — since FIX-163 coder is a sub-agent, not a valid task route; if LLM returns "coder" it now falls through to regex fallback

Test plan

  • python3 -m py_compile agent/dispatch.py — passes
  • python3 -m py_compile agent/classifier.py — passes
  • models.json valid JSON — confirmed
  • E2E benchmark run with Ollama tier — verify classifier seed=1 produces deterministic type routing
  • E2E with OpenRouter tier — verify seed forwarded in API call (check logs for consistent classification)

🤖 Generated with Claude Code

claude added 30 commits March 19, 2026 13:50
- Replace buf.build SDK deps with protobuf/httpx/connect-python from PyPI
- Generate bitgn harness and VM proto files manually
- Implement Connect RPC JSON client (bitgn/_connect.py)
- Add HarnessServiceClientSync and MiniRuntimeClientSync wrappers
- Switch to Python 3.12 (3.14rc2 incompatible with pydantic)
- Add OpenRouter support via .secrets file (gitignored)
- Fix UnboundLocalError bug: initialize txt before try/except
- Add secrets.example template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Based on benchmark analysis (Sonnet 42.86%, Qwen 14.29%), add:
- U1: Hardcoded tree/ + AGENTS.MD steps before LLM loop
- U2: Deep-exploration system prompt with few-shot examples
- U3: Pre-write validation of naming patterns (extension + prefix)
- U4: Hints on empty list results
- U5: Search count 5→10 + hints on empty search
- U6: Compaction preserves first 6 messages (tree + AGENTS.MD context)
- U7: Model-specific config (max_completion_tokens for small models)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- U8: Add two-level probe paths (docs/invoices, workspace/todos,
  records/todos, etc.) to discover dirs where parent has no direct files
- U9: Smart AGENTS.MD auto-ref — only add when content > 50 chars
  (prevents unexpected-ref penalty when AGENTS.MD is a pure redirect)
- U10: VM search fallback in delete detection for deeply nested files
  (e.g. notes/staging/) unreachable via outline()
- U11: Pre-load all skill/policy/config files from discovered dirs,
  re-extract path patterns from newly loaded skill file content
- Switch MODEL_ID to anthropic/claude-sonnet-4.6 via OpenRouter
- Add benchmark results: docs/anthropic-claude-sonnet-4.6.md

Result: 68.57% → 100.00% (7/7 tasks) on bitgn/sandbox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SCII guard, read-before-write, staging probe, expanded delete-detection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Key fixes applied:
- Fix-21: direct_finish_required flag blocks all non-finish actions on MISSING-AMOUNT
- Fix-22: Clean pre-delete hint (user message only, no fake assistant JSON)
- Fix-23: AGENTS.MD cache-hit finish hint when task unresolved
- Fix-24: Block writes without extension when existing files have extensions
- Fix-25: Intercept navigate.tree '/' at step>=1 when AGENTS.MD pre-loaded
- Fix-26: FORMAT NOTE in pre-loaded files message for exact format copying
- Fix-27: Retry loop (4 attempts, 4s sleep) for transient 503/502/NoneType errors
- all_reads_ever: only track successful reads to prevent cross-dir false positives

Result: qwen3.5:9b 100.00% on bitgn/sandbox (all 7 tasks scored 1.00)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-phase scaffolding bypasses 4b model's JSON/instruction-following
failures: force-finish after idle steps, invoice multi-pattern support,
MISSING-AMOUNT keyword autocorrect, unconditional redirect ref forcing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix-62: Auto-correct answer from AGENTS.MD keyword (direct, no redirect)
  for question tasks when 2b model ignores AGENTS.MD instructions
- Fix-62b: When FIX-62 triggered, filter refs to AGENTS.MD only (remove
  hallucinated paths model put in refs)
- Fix-28b: When nav-root loop AND direct_finish_required, use MISSING-AMOUNT
  keyword as force-finish answer (fixes t04 in full benchmark run)

qwen3.5:2b achieves 100.00% on bitgn/sandbox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docs/qwen3.5-2b.md: 100.00% result with Fix-62/62b/28b analysis
- docs/RESULT.md: updated comparison table with all 4 models (all at 100%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FIX-63: auto-list parent dir before first delete (loop.py)
- DELETED/WRITTEN/CREATED DIR explicit feedback (loop.py)
- main.py: per-task timing, итоговая статистика с проблемами по заданиям
- CLAUDE.md: уточнён путь tmp и требования к статистике
- docs/pac1-py-fixes.md: полный список применённых фиксов агента

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x t02/t13

- dispatch.py: add Anthropic SDK client (primary for Claude models), Ollama via
  OpenAI-compatible API as fallback, model routing helpers (is_claude_model,
  get_anthropic_model_id), keep OpenRouter for backward compat
- loop.py: TASK_TIMEOUT_S=180 (3-min per-task limit), _to_anthropic_messages()
  for Anthropic API format conversion (extracts system, merges consecutive same-role
  messages), _call_llm() routes Anthropic→Ollama with transient-error retry (FIX-27)
- prompt.py: t02 fix — "discard thread" must NOT read thread file, must NOT touch cards;
  t13 fix — rescheduling rule with concrete numeric example, explicit "8 days apart" invariant
- main.py: updated MODEL_CONFIGS with Ollama model names for qwen variants
- pyproject.toml + uv.lock: add anthropic>=0.86.0 dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n loop.py

- _call_llm: separate API errors (retry) from JSON parse errors (return None
  immediately without falling back to Ollama — Ollama fallback is for API failures only)
- Move Req_Read/Req_Write/Req_MkDir/Req_Move imports from inside loop body to
  module-level import statement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pported models

- dispatch.py: add probe_structured_output(), get_response_format(), _STATIC_HINTS dict,
  _CAPABILITY_CACHE, cached NextStep JSON schema; add Req_Context/ContextRequest dispatch
- loop.py: add _extract_json_from_text() for free-form JSON extraction; refactor
  _call_openai_tier to use nullable response_format (None = text extraction fallback);
  add OpenRouter tier with capability detection; add token usage tracking; FIX-W4 wildcard delete reject
- main.py: add response_format_hint and thinking_budget to MODEL_CONFIGS; token stats in summary table
- prephase/prompt/models: various fixes from broader diff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…for pac1-py

FIX-75 (classifier.py): Pre-task LLM classification via default model before
agent start. ModelRouter.resolve_llm() calls LLM to decide task type
(think/tool/longContext/default) and routes to appropriate model. Falls back
to regex classify_task() on any error.

FIX-76 (dispatch.py): Extract call_llm_raw() — lightweight 3-tier LLM call
(Anthropic→OpenRouter→Ollama) with FIX-27 retry, probe_structured_output(),
empty-response retry, and think-block stripping. Used by classify_task_llm().
Fixes: missing retry, duplicated routing, leaky abstraction, hardcoded
json_object without capability check, Anthropic content[0] bug.

Also: _select_model() dedup, max_tokens=500 for classification (qwen thinking
models need headroom), .env with multi-model config, .gitignore for plan files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…FIER env var

- FIX-85: add deepseek-v3.1:671b-cloud, deepseek-r1:671b-cloud, deepseek-v3:685b-cloud to MODEL_CONFIGS with appropriate ollama_think flags
- FIX-86B: read MODEL_CLASSIFIER env var and pass to ModelRouter.classifier for lightweight task classification routing
- Simplify: convert print string concatenation to pure f-string with inline conditional

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lassifier model

- FIX-83: add is_ollama_model() helper (name:tag, no slash) and use it in
  Tier 2 guard to correctly skip OpenRouter for deepseek-v3.1:671b-cloud
  and all other Ollama-format models (was: only qwen3.5: prefix matched)
- FIX-84A: add think: bool | None param to call_llm_raw; Ollama Tier 3 now
  respects explicit think=False to suppress <think> blocks that consumed the
  entire max_tokens budget leaving an empty response after strip
- FIX-84B: call classify_task_llm with think=False + max_tokens=200 to
  prevent think-block blowout on Ollama-backed classification calls
- FIX-86A: add classifier field to ModelRouter dataclass; resolve_llm uses
  it instead of default when set, enabling a cheap model for classification
  while routing actual tasks to heavier models

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rward reference

Pyright reported reportUndefinedVariable/reportRedeclaration because is_ollama_model
was defined after call_llm_raw which uses it. Moved definition before call_llm_raw.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…model modes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n LLM classification

Root cause: qwen3.5:cloud and similar models cannot disable thinking (think=False → empty).
With think=True + max_tokens=200, the think block exhausts the budget → empty after strip.

Fix: if ollama_think=True in model config, use think=None (cfg default) + max_tokens=2000.
Non-thinking models keep think=False + max_tokens=200.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Тип/Модель columns always appear

Previously single-model mode skipped ModelRouter entirely: no [MODEL_ROUTER] log lines,
no task_type in stats, stats table without Тип/Модель columns.

Now ModelRouter is always created. Stats table always uses the extended format.
Title shows "(multi-model)" only when different models are actually configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Column was always 0 for Ollama models. Removed from stats table, loop.py accumulators,
and main.py token_stats. Dead else-branch (single-model table format) also removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace bare regex chain with priority-ordered _Rule dataclass matrix.
Adds must/must_not conditions, bulk-scope patterns (remove all, delete all,
discard all, clean all), and keeps _LONG_CONTEXT_WORDS as backward-compat alias.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `_task_fingerprint()` helper that extracts matched keywords from
`_THINK_WORDS` and `_LONG_CONTEXT_WORDS` into a frozenset. Add
`_type_cache` field to `ModelRouter` and check it in `resolve_llm()`
before calling `classify_task_llm()` — skips the LLM round-trip when a
task with identical keyword set was already classified in this session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
classify_task_llm() gains optional vault_hint parameter appended to user
message; reclassify_with_prephase() now accepts model/model_config and
performs LLM re-class after FIX-89 rule-based pass, passing vault file
count and bulk-scope flag as structured context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
claude and others added 30 commits April 1, 2026 16:00
…d history to 400 chars

FIX-146: _extract_json_from_text() now collects ALL bracket-matched JSON
objects and returns the richest one (current_state+function > function-only >
first). Fixes multi-action Ollama responses where bare {"tool":"read"} was
extracted instead of the full NextStep object that followed it, causing writes
to be silently dropped.

FIX-147: _MAX_READ_HISTORY 200→400 chars. The next_follow_up_on field in
acct_001.json appears at ~240 chars; the 200-char limit cut it off in log
history, causing the model to re-read the file 15+ times per reschedule task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ove/mkdir

When the Ollama model generates multi-action text where the formal NextStep
schema has empty placeholder fields (path="", content=""), dispatching it
causes PCM to throw INVALID_ARGUMENT. Now detected before dispatch: injects
a correction hint with the expected path format instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…xtraction

Revised FIX-146: multi-action Ollama responses often end with report_completion
AFTER the actual writes. The previous priority (current_state+function first)
picked report_completion and skipped all pending writes.

New priority: mutations (write/delete/move/mkdir) > full NextStep non-report >
full NextStep any > function-only > first. Each step now executes the first
pending write, allowing subsequent steps to handle remaining writes naturally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… objects over no-tool objects

Some models (minimax-m2) emit "Action: Req_Read({"path":"..."})" without a
"tool" field inside the JSON. A new regex pre-pass detects the Req_XXX(
prefix before each { and injects the inferred tool name when absent.

Also adds priority tier 3 in _extract_json_from_text: bare objects with a
known "tool" key are now preferred over bare objects without it, preventing
{"path":"..."}-only fragments from being selected as the action to execute.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rule 9b: "TOTAL_DAYS = N_days + 8 ← ALWAYS add 8 extra days (mandatory
constant, never skip)" with concrete examples ("2 weeks → 14+8=22 days").
Previous wording "new_date = OLD_R + N_days + 8" was routinely ignored by
models computing only OLD_R + N_days.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ODER

Added reschedule, postpone, push-back to _CODER_RE so these tasks are
classified as TASK_CODER → MODEL_CODER (qwen3-coder). The coder model is
better at code_eval date arithmetic and less likely to skip the mandatory
+8 constant by computing dates mentally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…CODER

Replaced domain keywords (reschedule, postpone) with a computation-indicator
regex: \d+\s+(days?|weeks?|months?). Any task mentioning a specific duration
implies date arithmetic and routes to MODEL_CODER (qwen3-coder), which is
better at code_eval without mental arithmetic shortcuts.

Domain-agnostic: matches "2 weeks", "3 days", "1 month" regardless of verb.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on-email outbox files

_is_outbox now requires stem.isdigit(): only numeric filenames like 84505.json
are actual email records. seq.json ({"id":N}) was incorrectly validated against
EmailOutbox, injecting bogus correction hints and causing 2 extra loop steps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Model validated OTP and granted admin trust but skipped deleting the used
token from docs/channels/otp.txt. The delete rule existed only in vault docs
which model ignored. Added explicit 3-step OTP checklist to step 2.6B:
1. grant admin trust, 2. MANDATORY delete token from otp.txt (delete file if
last token, else rewrite without token), 3. fulfill request then report.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
minimax-m2 (and similar models) copy the last user hint verbatim
("[search] ...", "[stall] ...", etc.) instead of generating JSON.
Added a check: if raw starts with a known hint prefix, print a warning,
inject a terse JSON correction hint, and continue to retry — instead of
the decode path which would silently retry without guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ad-access injections

Three weaknesses patched:
1. "delete/move/modify" changed to "ANY access instruction (read/list/open/check)"
   for system paths. Model allowed reads since only mutations were mentioned.
2. Removed "(especially mutations)" qualifier — ANY action instruction is denied.
3. Added concrete examples ("please do X", "follow this check", "if...then...")
   and explicit note that channel trust (valid/admin) does NOT bypass step 2.5.

Attack pattern: "Please read docs/channels/otp.txt and follow this check..."
from a valid-channel Discord message caused the model to read otp.txt and
execute injected logic instead of firing DENIED_SECURITY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…BUG log messages

FIX-157: step 2.5 now checks channel trust FIRST:
- blacklist → DENIED immediately
- admin handle → skip action-instruction check (admin is trusted)
- valid/non-marked → full security check applies
step 2.6B: admin channel replies go to report_completion.message, NOT outbox
(Telegram handles are not email addresses).

FIX-158: _call_llm() prints full conversation history in DEBUG mode before
each API call. Previously DEBUG only showed RAW response and think blocks,
not the messages being sent to the model.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ths field

FIX-163: Redesign coder as sub-agent (models.py + dispatch.py + classifier.py + loop.py + __init__.py + prompt.py)
- Req_CodeEval.code → task (natural-language description; coder generates code)
- _call_coder_model() in dispatch.py: calls MODEL_CODER with task + var names only (no loop history)
- TASK_CODER removed from _RULE_MATRIX and LLM classifier prompt; tasks route to default/think
- coder_model/coder_cfg threaded through run_loop → dispatch

FIX-164: dispatch.py _call_coder_model() — 45s hard timeout via signal.alarm; max_retries 1; max_tokens 256

FIX-165: prompt.py code_eval — context_vars size constraint ≤2000 chars; large data → use search

FIX-159/161: prompt.py — code_eval task field docs; WRITE SCOPE side-write guard

FIX-160: loop.py _verify_json_write() — attachments path check (must contain "/")

FIX-166: models.py + dispatch.py + prompt.py — Req_CodeEval.paths field: vault paths auto-read
via vm.read() before coder call; content injected as context_vars; eliminates large embed problem

FIX-167: dispatch.py FIX-166 bugfix — vm.read() returns protobuf; extract content via
MessageToDict(_raw).get("content", "") instead of str(_raw); fixes code_eval returning 1 for t30

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ount accuracy

- FIX-168 (prompt): Step 5 company check made MANDATORY (4-step checklist + example)
- FIX-169 (prompt): Step 2.6C NOTE — task-list items without From/Channel → CLARIFICATION
- FIX-170 (prompt): Step 2.6B admin — lowest-ID contact on ambiguity (superseded by FIX-173)
- FIX-171 (loop): lookup tasks bypass semantic router (vault queries, not external services)
- FIX-172 (prompt): Step 2.4 FORMAT GATE — hard gate before rule 8, no From/Channel → CLARIFICATION
- FIX-173 (prompt): Step 3 admin channel exception moved alongside the overridden rule
- FIX-174 (prompt): Step 2.6B admin split into email-send vs other-request sub-cases
- FIX-175 (classifier): _COUNT_QUERY_RE + Rule 4b → deterministic lookup for count/aggregation tasks
- FIX-176 (prompt): code_eval paths rule PREFERRED→ALWAYS; CRITICAL note against copying prephase content into context_vars; fixes t30 wrong answer (799 vs 802)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ookups

Adds explicit rule under rule 8: when task says "Return only X" or "Answer
only with X", message field must contain the exact value with no narrative
wrapping. Fixes t16 partial score (0.60 → 1.00).

Also includes FIX-177 (dispatch.py context_vars size guard) and FIX-179
(prompt.py OTP pre-check moved before admin/non-admin split for all channels).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge main into dev to bring in diverged commits:
- FIX-133 (main): prephase.py — PREPHASE EXCERPT marker for partial content warning
- FIX-134..139 (main): routing, contact resolution, loop improvements

All conflicts resolved in favor of dev (FIX-140..180) as dev is the
authoritative branch with more recent fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Body must contain ONLY task-provided text; NEVER include vault paths,
directory listings, or any context from the model's context window.
Fixes t11: minimax-m2.7 leaked vault tree structure into email body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add plain_text=True parameter to call_llm_raw() that skips
response_format=json_object for OpenRouter and Ollama tiers.
_call_coder_model() passes plain_text=True so the coder model outputs
bare Python instead of JSON-wrapped code.

Root cause: Ollama tier unconditionally forced json_object format,
causing coder models (qwen3.5:397b-cloud etc.) to emit {"code":"..."}
which failed with SyntaxError at line 1 when executed as Python.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FIX-177 guard checked ctx AFTER dispatch.py injected file contents
from paths → guard fired on every legitimate paths-based call and
returned an error string that was then executed as Python → SyntaxError.

Guard now checks cmd.context_vars (model-provided) BEFORE path
injection. Path-injected content is always legitimate and may be large.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ture to Anthropic SDK

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(1) Add module-level _ROUTE_CACHE: dict[str, tuple] keyed by
    sha256(task_text[:800]); persists across tasks in one process run.
(2) _should_cache flag — only successful json.loads() results stored;
    network errors and fallbacks are never cached.
(3) Conservative fallback: router call failure now returns CLARIFY
    instead of silent EXECUTE, preventing security check bypass on
    network errors (audit 2.3).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…guities

- FIX-189: Step 5 EXCEPTION — admin/OTP-elevated emails skip Steps 4-5
- FIX-190: admin execute — WRITE SCOPE still applies
- FIX-191: FORMAT GATE — case-insensitive header matching
- FIX-192: OTP token format + trust level source clarified
- FIX-193: current_state ≤15 words; contact ID numeric sort
- FIX-194: month conversion table; precision units rule

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _LoopState dataclass (8 state vars + 7 token counters).
Extract _run_pre_route() (~115 lines): injection detection + semantic routing.
Extract _run_step() (~260 lines): one loop iteration, all pre/post-dispatch logic.
Extract _st_accum() helper: consolidates 3 duplicate 6-line token accumulation blocks.
run_loop() reduced from 418 lines to 29 lines (thin orchestrator).
Zero behavior change — pure structural refactor. Resolves audit 2.5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor(loop): FIX-195 — decompose run_loop() God Function
…e cleanup

FIX-196: models.json — fix seed documentation (seed=0 means random in Ollama,
classifier now uses seed=1 for actual determinism)
FIX-197: dispatch.py — forward seed to OpenRouter tier via create_kwargs["seed"]
for cross-tier deterministic sampling; Anthropic SDK has no seed param (comment)
FIX-198: classifier.py — remove TASK_CODER from _VALID_TYPES and
_PLAINTEXT_FALLBACK (coder is a sub-agent since FIX-163, not a task route)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants