Skip to content

fix(gemma_policy): add craft category + narrow inventory_query keyword scope - #8

Open
Pablomonte wants to merge 239 commits into
nicoechaniz:mainfrom
Pablomonte:fix/gemma-policy-craft-category
Open

fix(gemma_policy): add craft category + narrow inventory_query keyword scope#8
Pablomonte wants to merge 239 commits into
nicoechaniz:mainfrom
Pablomonte:fix/gemma-policy-craft-category

Conversation

@Pablomonte

Copy link
Copy Markdown

Summary

Vendored copy of `agents/gemma_policy.py` from `nicoechaniz/DaemonCraft` is in `tools/gemma_policy.py` of this repo (used by `tools/embodied_plan_tool.py` when DaemonCraft is the active platform).

The L4 keyword classifier has two collaborating bugs that prevent any craft-style intent from dispatching the actual crafting tools:

  1. No 'craft' category exists in `CATEGORY_KEYWORDS`. Captain-side intents like `Craft a stone shovel using cobblestone and sticks` have nothing to match.
  2. 'inventory_query' keyword 'inventory' is too broad — it matches any intent that mentions the word, including the natural "…from my inventory" clause that captains routinely emit.

The `inventory_query` tool subset is only `[get_inventory, ask_clarification, report_execution_error]`, so the captain ends up with no actionable body tools at all and tells the player "no tengo capacidad de craftear".

Observed live

End-to-end trace from a Hermes-K2.6 + DaemonCraft session:

```
Captain (Kimi-K2.6) → embodied_plan(intent=
"Craft a stone shovel using cobblestone and sticks from my inventory."
)
→ policy.classify_category() loops CATEGORY_KEYWORDS
→ hits 'inventory_query' via keyword 'inventory'
→ allowed_tools = [ask_clarification, get_inventory, report_execution_error]
→ Gemma-Andy plan: ask_clarification("craft_item is not available
in this turn; should I wait?")
→ captain tells player: "no tengo capacidad de craftear ahora"
```

The fix (additive)

1. New 'craft' category, inserted before mining/build

```python
("craft", ["craft ", "craftear", "crafteá", "craftea",
"fabricar", "fabricá", "fabrica",
"smelt", "fundir", "fundí", "fundi",
"cocinar", "cociná", "cocina",
"horno", "furnace", "forja", "forjar", "forge"]),
```

Tool subset (10 tools + the 2 COMMON_SAFE = 12 total):

```python
"craft": ["get_inventory", "view_craftable", "craft_item", "smelt_item",
"check_furnace", "take_from_furnace", "equip_item",
"place_block", "scan_nearby", "goto"],
```

Includes `place_block` for placing a crafting_table/furnace first, and `goto`/`scan_nearby` for finding existing crafting stations nearby.

Mapped through `STRATEGY_MAP["craft"] = "embodied_plan"` like every other category.

2. Narrow 'inventory_query' keyword scope

```diff

  • ("inventory_query", ["inventario", "inventory", "decime qué tenés", ...
  • ("inventory_query", ["inventario", "decime qué tenés", ...
  •                   \"list inventory\", \"list my items\"]),
    

```

Drop bare `"inventory"` — too greedy. Pure inventory-show intents still classify correctly via `"inventario"` (Spanish-only), `"show inventory"`, `"list inventory"`, etc.

3. Extend 'build' tools + keywords

Bonus cleanup surfaced while fixing #1:

```diff

  • "build": ["scan_nearby", "goto", "place_block", "equip_item", "get_inventory"],
  • "build": ["scan_nearby", "goto", "place_block", "fill_volume", "ignite",
  •       \"equip_item\", \"get_inventory\"],
    

```

`fill_volume` enables bulk construction without dropping category. `ignite` covers torch/lava placement scenarios.

Build keywords also extended with `"fill "`, `"rellena/rellená"`, `"construir"` (the infinitive — only imperative forms were covered).

Verification

```python

p = GemmaPolicy()
for it in [
... "Craft a stone shovel using cobblestone and sticks from my inventory.",
... "craft 4 oak planks",
... "fundir 16 arena en vidrio",
... "construir una casa de 5x5",
... "fill volume con cobblestone",
... "mostrame el inventario",
... "inventory check",
... ]:
... cat = p.classify_category(it.lower())
... n = len(p.get_allowed_tools(cat) or [])
... print(f"{cat:18s} [{n:>3}]")

craft [ 12]
craft [ 12]
craft [ 12]
build [ 9]
build [ 9]
inventory_query [ 3]
default [ 38]
```

Before this patch, the first three returned `inventory_query 3`, so the agent literally could not dispatch `craft_item`.

Coordination with the canonical source

This is the vendored upstream copy of DaemonCraft's `agents/gemma_policy.py`. The DaemonCraft canonical source has the identical patch at nicoechaniz/DaemonCraft#16. The two should land together to keep the mirror in sync.

Test plan

  • Manual: `@AsciiProbe haceme una pala de piedra` now dispatches `craft_item` instead of looping on `ask_clarification`.
  • Manual: `mostrame el inventario` still resolves to `inventory_query` (3 tools — correct narrow scope).
  • Follow-up: add unit tests for the build/craft/inventory keyword collision matrix (no current test fixture covers this).

When tui.history_nav_requires_empty_input is true, Up/Down arrows
no longer cycle history/queue if the composer input has text.
This makes multiline editing less surprising: arrow-up on the first
line of a non-empty buffer stays in the buffer instead of jumping
to the previous history item.

Also adds ConfigTuiConfig to gatewayTypes so the TUI can read the
setting from config.get full responses.
…anscript corruption

Adds _sanitize_unanswered_tool_calls() to backfill synthetic role=tool
results for any assistant tool_calls that weren't answered before an
interrupt or error exits the loop. This prevents the next API call from
failing with a missing tool response error.

Also removes the duplicated inline logic from the outer-loop error
handler and calls the helper from _persist_session() so every exit
path guarantees an API-valid transcript.
When a user interrupts with an image paste/attachment, the payload is a
tuple (text, images). The post-interrupt re-queue code did
'\n'.join(all_parts) which crashed with:

  TypeError: sequence item 0: expected str instance, tuple found

Split text and images from each part, combine text with join, and
preserve image attachments so process_loop can unpack them normally.
Recovers input_max_lines, collapse_large_pastes, history_nav_requires_empty_input,
and show_full_input that were accidentally dropped during Kimi cleanup (bbb91391).
…ture pinning

- Reads Kimi CLI tokens from ~/.kimi/credentials/kimi-code.json
- resolve_kimi_coding_runtime_credentials(): OAuth first, refresh token support, fallback to KIMI_API_KEY
- kimi_coding_default_headers(): proper User-Agent and X-Msh-* headers for coding endpoint
- kimi_coding_required_temperature(): pins temperature to 0.6 for kimi-k2.6 on coding endpoint
- 401 retry with token refresh before aborting
- Integrates into run_agent.py and auxiliary_client.py
The upstream _fixed_temperature_for_model() already omits temperature for
Kimi models, letting the server choose the correct value. Our manual
0.6 pinning was unnecessary and could conflict with server-side mode
selection (thinking vs non-thinking). Verified working without it.
The auxiliary client's _refresh_provider_credentials() handled auth
refresh for Codex, Nous, and Anthropic, but not for Kimi. When the
Kimi OAuth token expired, auxiliary calls (memory flush, compression,
session search) failed with HTTP 401 while the main client recovered
automatically.

Add a kimi-coding / kimi-coding-cn case that calls
resolve_kimi_coding_runtime_credentials(force_refresh=True) and evicts
the cached auxiliary client, mirroring the existing provider refresh
paths.

Fixes auxiliary memory flush failures when using Kimi OAuth.
# Conflicts:
#	cli.py
#	hermes_cli/config.py
# Conflicts:
#	ui-tui/src/app/interfaces.ts
#	ui-tui/src/app/uiStore.ts
#	ui-tui/src/app/useConfigSync.ts
- Add protect_first_n to DEFAULT_CONFIG['compression'] (default 3, allows 0)
- Add compression.prompt {preamble, template} for custom summary prompts
- Bump config version 22 -> 23
- Extract hardcoded prompt constants in ContextCompressor to module-level defaults
- Pass custom preamble/template through ContextCompressor constructor
- Read protect_first_n and prompt config in run_agent.py, pass to compressor
- Update status display to show protect_first_n
- Add tests for protect_first_n=0 and custom prompts
- Always preserve system prompt as literal even when protect_first_n=0
- Fix missing import resolve_kimi_coding_runtime_credentials in runtime_provider.py
- Update wiki and website docs
# Conflicts:
#	hermes_cli/config.py
#	tests/agent/test_auxiliary_client.py
#	tests/hermes_cli/test_runtime_provider_resolution.py
#	ui-tui/src/app/uiStore.ts
#	ui-tui/src/app/useConfigSync.ts
# Conflicts:
#	hermes_cli/config.py
…_messages transport

PR NousResearch#12846 enabled Anthropic prompt caching for third-party gateways,
but gated it on is_claude, which excluded providers like MiniMax
that serve their own model families (MiniMax-M2.7, etc.) through the
native Anthropic protocol.

MiniMax documents full cache_control support on its /anthropic
endpoints (global and China). This patch adds MiniMax detection to
_anthropic_prompt_cache_policy() using:

- Built-in provider id (minimax, minimax-cn), or
- Known Anthropic-compatible hostname (api.minimax.io,
  api.minimaxi.com)

Both paths receive the native cache_control layout.

Refs: NousResearch#8294 (related, but only covered Claude-named models on
third-party gateways).
Closes NousResearch#17332
AIAgent.__init__ now detects provider=minimax/minimax-cn and defaults to:
- api_mode='anthropic_messages' (was 'chat_completions')
- base_url='https://api.minimax.io/anthropic' or 'https://api.minimaxi.com/anthropic'

This ensures prompt caching (and all other Anthropic-protocol features)
work out of the box for AIAgent users, not just CLI users.

Previously, AIAgent(provider='minimax') fell through to chat_completions
because base_url was empty and there was no provider-name detection for
MiniMax in the api_mode resolution logic. The CLI already resolved this
correctly via runtime_provider.py; this change mirrors that behaviour in
the low-level agent constructor.

Tests added:
- 5 new tests in test_minimax_provider.py covering defaults, cn variant,
  explicit base_url preservation, explicit api_mode override, and
  prompt caching enabled by default.
- 2 new tests in test_anthropic_prompt_cache_policy.py covering empty
  base_url with provider=minimax/minimax-cn.
…base_url

PR NousResearch#17425 (merged) enabled prompt caching for MiniMax models on the
anthropic_messages transport, but users still had to manually configure
both api_mode and base_url to actually benefit from it.

This patch makes the defaults ergonomic:

- AIAgent.__init__ now auto-detects provider=minimax / minimax-cn and
  defaults to api_mode=anthropic_messages + the correct /anthropic base_url
  (global or China endpoint respectively).
- .env.example suggests the /anthropic endpoints instead of /v1.
- Explicit base_url or api_mode are preserved when the user sets them.

Tests: 5 new cases covering both providers, explicit overrides, and
prompt-caching flags.

Refs: NousResearch#17332, NousResearch#17333, NousResearch#17425
nicoechaniz and others added 22 commits May 16, 2026 07:35
After a failed /intent call with spatial error types (target_occupied,
no_solid_neighbor, bot_in_target), retry once with previous_error payload
so Gemma-Andy can replan around the obstacle.

Refs: t_336ffcd5
Documents the current working prototype where Grok web generates
shell snippets (CMD: prefix) that the proxy executes locally and
pipes back. Includes open questions and next steps.
Documents the current working prototype where Grok web generates
shell snippets (CMD: prefix) that the proxy executes locally and
pipes back. Includes open questions and next steps.
Copied STRATEGY_MAP and NEEDS_SETUP from DaemonCraft gemma_policy.py.
embodied_plan_tool.py now includes strategy and needs_setup in
mitigation output so callers can auto-select execution path.

Navigation → embodied_plan with narrow tools.
Building → embodied_plan with needs_setup flag.
Fallback → mc_direct when Andy fails.
# Conflicts:
#	CHANGELOG.md
#	agent/auxiliary_client.py
#	gateway/platforms/daemoncraft.py
#	tests/tools/test_embodied_plan_tool.py
#	tools/embodied_plan_tool.py
…d scope

GemmaPolicy's L4 classifier had two collaborating bugs that made any
craft-style intent unable to dispatch the actual crafting tools.

(1) No 'craft' category existed. Intents like
    "Craft a stone shovel using cobblestone and sticks from my inventory"
    had nothing to match in CATEGORY_KEYWORDS.

(2) 'inventory_query' keyword 'inventory' was too broad — it matched
    any intent that mentioned the word, including the perfectly natural
    "...from my inventory" clause that captains emit constantly. The
    inventory_query tool subset is only [get_inventory, ask_clarification,
    report_execution_error], so the captain ended up with no actionable
    body tools at all and replied "no tengo capacidad de craftear".

Observed live with Hermes-K2.6 + DaemonCraft:
  Captain → embodied_plan(intent="Craft a stone shovel ... from my inventory.")
  → policy.classify_category() → 'inventory_query' (keyword 'inventory')
  → allowed_tools = [ask_clarification, get_inventory, report_execution_error]
  → Gemma-Andy plan: ask_clarification("craft_item not available; should I wait?")
  → captain explains "no tengo capacidad" to the player

Three additive changes:

1. New 'craft' category, inserted before mining/build:
   keywords = craft / craftear / fabricar / smelt / fundir / cocinar /
              horno / furnace / forja / forge
   tools = [get_inventory, view_craftable, craft_item, smelt_item,
            check_furnace, take_from_furnace, equip_item,
            place_block, scan_nearby, goto]
   STRATEGY_MAP entry → embodied_plan

2. Narrow 'inventory_query' keyword scope: drop bare 'inventory',
   keep 'inventario' (Spanish), 'show inventory', 'list inventory',
   'list my items', and the existing Spanish phrases.

3. Extend 'build' tools with fill_volume + ignite and keywords with
   'fill ', 'rellena/rellená', 'construir' (the infinitive — only
   imperative forms were covered).

Verification

    >>> for it in [
    ...     "Craft a stone shovel using cobblestone and sticks from my inventory.",
    ...     "craft 4 oak planks", "fundir 16 arena en vidrio",
    ...     "construir una casa de 5x5", "fill volume con cobblestone",
    ...     "mostrame el inventario", "inventory check",
    ... ]:
    ...     cat = p.classify_category(it.lower())
    ...     n = len(p.get_allowed_tools(cat) or [])
    ...     print(f"{cat:18s} [{n:>3}]")

    craft              [ 12]
    craft              [ 12]
    craft              [ 12]
    build              [  9]
    build              [  9]
    inventory_query    [  3]
    default            [ 38]

Before this patch, the first three returned `inventory_query 3`, so the
agent literally could not dispatch craft_item.

This is the vendored upstream copy of DaemonCraft's agents/gemma_policy.py.
The DaemonCraft canonical source is at nicoechaniz/DaemonCraft PR nicoechaniz#16 with
the same patch applied — they should land together.
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.

3 participants