Phase 7 verify finding from TC #26 against :model cloud:
HTTP 400 from openrouter→Amazon Bedrock:
"tools.0.custom.name: String should match pattern
'^[a-zA-Z0-9_-]{1,128}$'"
Anthropic via Bedrock validates tool names against that regex and
rejects dots. PHASE2 originally chose "." as the namespace separator
("boltzmann.list_dir"); OpenAI tolerated it, Bedrock does not.
Separator switched to "__" (two underscores) everywhere — internal
API matches on-wire shape, no transformation layer:
- repl.lua:
- tools_schema builds "alias__name"
- dispatch_tool_call splits via "^(.-)__(.+)$" (non-greedy → leftmost __)
- :mcp tool parser uses same split
- :mcp tools formatter prints "alias__name"
- HELP block shows <alias__name>
- safety.lua confirm_tool_call: alias.* glob → alias__* glob
- config.lua example block: keys rewritten
- docs/PHASE2.md: amendment header added; §1, §2 row, §3 config.lua
row, §5 wire-shape JSON examples, §6 auto_approve schema, §7
meta-cmd table, §12 plan all updated. Original "." references
preserved in commit history.
Constraint: aliases must not themselves contain "__" so the parse
stays unambiguous. Tool names from MCP servers may have underscores
freely.
Second fix bundled — uninformative broker error:
Previously "broker error: transport: HTTP response code said error"
Now "broker error: transport: HTTP 400: {full body snippet}"
ffi/curl.lua M.post_sse changes:
- FAILONERROR no longer set (was hiding the response body).
- raw_body accumulator added alongside the SSE buffer; captures
every byte regardless of SSE shape.
- After perform, check status_code via curl_easy_getinfo. On >=400,
return (nil, "HTTP <code>: <body[:400]>"). 2xx unchanged.
- End-of-stream SSE flush only runs on 2xx (no false event on
error bodies that aren't SSE-shaped).
- Phase 1 callers reading just first return slot stay correct.
End-to-end verified:
- :model cloud + tools=[boltzmann__read_file ...] +
"Use boltzmann__read_file with path=/etc/hostname" →
Claude emits tool_call with name="boltzmann__read_file",
args='{"path": "/etc/hostname"}'. ok=true, transport clean.
- Force-bad tool name "bad.name.with.dots" → err string carries
the full bedrock 400 with the regex-pattern message visible.
TC #26 (sub-loop end-to-end) is now testable against cloud — the
error that blocked it is resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 commit #7 (final) per docs/PHASE2.md §12. Two changes bundled:
(1) commented-out mcp = {...} example block (~40 lines) at the end of
config.lua showing the Phase 2 schema:
- mcp.servers — alias → {url, auth_token | auth_env}
- mcp.auto_approve — "<alias>.<tool>" or "<alias>.*" globs
- mcp.max_tool_depth — sub-loop budget per ask_ai turn
The block is OFF by default; uncomment + adjust per fleet to
activate. Documentation-only; no behavior change to existing
configs (mcp_sessions stays empty, tools_schema() returns [],
broker omits the field — full Phase 1 compatibility).
(2) User-authored: deep model preset switched from
mistral-nemo-12b-instruct to qwen3-30b-a3b-instruct, with a 10-min
timeout_ms accommodating the larger model's RK3588 inference time.
Reason: nemo backend is dormant per the proxy /v1/models discovery
(aish#23 now returns 404 cleanly for unknown models instead of
silent fallback); qwen3-30b is the practical "deep" alternative.
Phase 2 implementation is now complete — 7 of 7 commits landed:
#16c194de mcp.lua + ffi/curl status_code + PHASE0 §4 amendment
#20fde77f safety.lua confirm_tool_call
#37c221a8 context.lua tool turns + use_tool_role fallback
#4c736d0e renderer.lua tool-call frames
#5efdc728 broker.lua opts.tools + tool_call accumulator
#67e9cfff repl.lua sub-loop + :mcp meta + system-prompt block
#7 (this) config.lua example + deep model switch
Next phase-loop step: verify (Phase 7). Files written are wired and
isolated-tested; end-to-end model-driven verification waits on either
a more compliant model or explicit forcing of tool_calls from the
prompt — known to be marginal with the loaded qwen-1.5b but proven
correct against direct probes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves issue #12 by partial-accept of the recommendation.
What landed:
- Single broker URL: http://hossenfelder.fritz.box:8082 for all three
presets (fast / deep / cloud). Server-side model-aware routing; no
client-side cloud auth (proxy holds the OpenRouter bearer).
- Models from hossenfelder's /v1/models inventory:
fast -> qwen2.5-coder-1.5b-q4_k_m.gguf (boltzmann local)
deep -> mistral-nemo-12b-instruct (boltzmann local)
cloud -> anthropic/claude-haiku-4.5 (OpenRouter route)
- `cloud` was already pointing at hossenfelder but with https://; flipped
to http:// so it matches the proxy's actual scheme.
What deferred:
- Schema rename `models` -> `brokers` (and the 5-cloud-preset shape
suggested in #12) — would touch repl.lua + broker.lua. Not blocking
Phase 7. If multi-preset becomes useful in practice, file a separate
issue for the rename then.
Phase 7 verification (live broker test):
- broker.chat(fast, [user="say pong"]) -> "CMD: echo pong" in ~3s
- multi-turn arithmetic (7*8=56, *2=112) preserved across turns
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README, .gitignore, CLAUDE.md (project conventions)
- docs/PHASE0.md — full Phase 0 manifest (locked substrate)
- 10 root .lua modules + 4 ffi/ bindings, all stubs raising NotImplemented
with module-scoped responsibilities matching the manifest
- config.lua wired to current dirac/hossenfelder endpoints (qwen-coder-7b
snappy/32k + cloud via OpenRouter through hossenfelder)
File names match docs/PHASE0.md §4 exactly. Module bodies fill in across
later phases; the tree shape is locked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>