Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions docs/spec/SPEC-002-session-lifecycle-and-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,25 @@ This enables:

## Turn Lifecycle

1. `SendUserMessage` command accepted after policy checks.
2. Actor appends user message to `SessionState.History`.
3. Actor invokes configured `IChatClient` via `ChatMessageConverter`.
4. Actor persists `TurnRecorded` event and applies to state.
5. Actor emits typed `SessionOutput` events to subscribers.
6. Actor checks compaction threshold.
1. `SendUserMessage` passes policy and complete input compatibility checks.
2. Actor appends the user message to `SessionState.History`.
3. Actor checks active history again before each model call.
4. Actor invokes the configured `IChatClient` via `ChatMessageConverter`.
5. Actor persists the `TurnRecorded` event and applies it to state.
6. Actor emits typed `SessionOutput` events to subscribers.
7. Actor checks the compaction threshold.

### Model Input Compatibility

The actor checks all active media references against the main model input
modalities. The check includes recovered history, new input, buffered input,
and tool-result media. An unknown persisted modality fails closed.

The actor rejects incompatible new input before it changes the session state.
It checks again before each model call to protect paths that add media during a
turn. The actor emits `ErrorCategory.InputCompatibility` with the unsupported
modalities and recovery guidance. It does not call the primary client,
fallback client, or provider when this local check fails.

### Tool Execution Pipeline

Expand Down
36 changes: 33 additions & 3 deletions docs/spec/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,29 @@ so changing Main or Fallback does not destroy overrides belonging to the previou
"Models": {
"Definitions": {
"qwen-main": {
"Provider": "remote-gpu",
"ModelId": "qwen3:30b",
"ContextWindow": 32768
"Provider": "remote-gpu",
"ModelId": "qwen3:30b",
"ContextWindow": 32768
},
"qwen-small": {
"Provider": "remote-gpu",
"ModelId": "qwen3:8b",
"ContextWindow": 32768
},
"qwen-vision": {
"Provider": "remote-gpu",
"ModelId": "qwen2.5-vl:7b",
"InputModalities": "Text, Image",
"OutputModalities": "Text"
}
},
"Roles": {
"Main": "qwen-main",
"Fallback": "qwen-small",
"Compaction": "qwen-small"
},
"Proxies": {
"Image": "qwen-vision"
}
}
}
Expand All @@ -146,6 +155,18 @@ so changing Main or Fallback does not destroy overrides belonging to the previou
| `InputModalities` | string? | `null` | Manual override for input modalities. Comma-separated flags from `Text`, `Image`, `Audio`, `Video` — e.g. `"Text"` or `"Text, Image"`. When set, bypasses automated capability detection. |
| `OutputModalities` | string? | `null` | Manual override for output modalities. Same form as `InputModalities`. |

`Models.Proxies.Image` references one named definition. The definition must accept text and image input, and it must produce text output.

The image proxy lets a text-only main model use approved image attachments. Netclaw sends each image to the proxy without session history or tools.

Netclaw stores the proxy description as a durable session event. The main model receives that description as untrusted text.

Netclaw keeps the original media reference. An image-capable main model still receives the original image.

Netclaw also analyzes an old unprocessed image before the next main model call. A proxy failure stops the turn before a main or fallback call.

Use `netclaw model set image-proxy <provider> <model-id>` to set the proxy. Use `netclaw model clear image-proxy` to clear it.

### Session

Tuning parameters for LLM session behavior.
Expand Down Expand Up @@ -558,11 +579,20 @@ export NETCLAW_Session__MaxToolIterationsPerTurn="60"
"qwen-small": {
"Provider": "local",
"ModelId": "qwen3:8b"
},
"qwen-vision": {
"Provider": "local",
"ModelId": "qwen2.5-vl:7b",
"InputModalities": "Text, Image",
"OutputModalities": "Text"
}
},
"Roles": {
"Main": "qwen-main",
"Compaction": "qwen-small"
},
"Proxies": {
"Image": "qwen-vision"
}
},
"Session": {
Expand Down
10 changes: 10 additions & 0 deletions evals/run-evals.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,13 @@ assert_skill_operations_diagnostics() {
stdout_contains '\[tool:call\]'
}

assert_skill_image_proxy_configuration() {
daemon_log_skill_loaded_via_skill_tool 'netclaw-operations' \
&& stdout_response_contains 'model set image-proxy' \
&& stdout_response_contains 'Text.*Image' \
&& stdout_no_skill_file_read_called
}

assert_skill_citation_search() {
# Model should actually search when asked to search.
stdout_contains '\[tool:call\] web_search'
Expand Down Expand Up @@ -1667,6 +1674,9 @@ run_all() {
"My session seems broken, help me fix it" \
"Debug my Netclaw session"

run_case skill_image_proxy_configuration "knows the image proxy command and required modalities" \
"My main model accepts only text. How do I configure a smaller model to describe image attachments? Include the exact Netclaw command and required modalities."

run_case skill_citation_search "performs web search when asked" \
"Search the web for the latest Akka.NET release" \
"Look up the current version of Akka.NET"
Expand Down
2 changes: 1 addition & 1 deletion feeds/skills/.system/files/netclaw-operations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: netclaw-operations
description: "REQUIRED when the user asks about scheduling, reminders, cron jobs, timers, background jobs, diagnostics, troubleshooting, MCP tools, daemon health, identity updates, or Netclaw capabilities and self-maintenance."
metadata:
author: netclaw
version: "2.39.0"
version: "2.40.0"
---

# Netclaw Operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,48 @@ still read. `model list` reports an unparseable config instead of crashing.
`netclaw doctor --fix` applies only repairs it can derive safely; it does not
invent missing named definitions or role assignments.

### Assigning an image proxy

Use an image proxy when the main model accepts text but does not accept images:

```bash
netclaw model set image-proxy <provider> <model-id>
netclaw model list
```

The command stores the model as a named definition. It also assigns `Models.Proxies.Image` to that definition.

The proxy definition must accept text and image input. It must produce text output.

Netclaw validates these modalities at startup. An invalid or unknown proxy definition stops startup with a configuration error.

Netclaw sends one image and a fixed prompt to the proxy. The proxy call has no session history and no tools.

Netclaw stores the description in the session journal. A text-only main model receives the stored description as untrusted text.

Netclaw keeps the original media reference. A later image-capable main model receives the original image.

The next turn also processes an old image that lacks a stored proxy result. A proxy failure stops the main and fallback calls.

Remove the assignment with this command:

```bash
netclaw model clear image-proxy
```

### Session input compatibility errors

A saved session can contain image, audio, or video input from an earlier model.
Netclaw checks the complete active history before each model call. If the new
main model lacks a required modality, the turn stops before any provider or
fallback call.

The error names the unsupported modalities and the active model. Select a model
that accepts those modalities, or configure an image proxy for image-only gaps.
Start a new conversation for unsupported audio or video history. Do not diagnose
this result as a provider outage. Netclaw also rejects an unknown saved modality
value instead of omitting that media.

### Adding GitHub Copilot

GitHub Copilot uses the OAuth device flow only — no API key. The operator
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/named-image-modality-proxy/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-31
122 changes: 122 additions & 0 deletions openspec/changes/named-image-modality-proxy/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
## Context

Named model definitions own model metadata, but the resolver now discards their names.
The daemon builds clients only for the role assignments.
Session media records preserve original image files across actor recovery.

A text-only main model cannot consume those image records.
The proxy must create text without the full model-router design from issue `#648`.

## Goals / Non-Goals

**Goals:**

- Resolve any named model definition through one runtime registry.
- Reuse the current provider client factory and capability resolver.
- Let one named image proxy create a durable description.
- Preserve the original image as the authoritative session media.
- Support new attachments and old session images.
- Add fail-closed CLI, TUI, schema, and startup validation.

**Non-Goals:**

- Add audio or video proxies.
- Add subagent model assignments.
- Add per-turn route policy or load balance.
- Send session history or tools to the proxy.
- Add image crop or follow-up analysis tools.

## Decisions

### Extend the canonical named model shape

`NamedModelConfiguration` will add `Proxies.Image` as an optional definition name.
The resolver will retain a case-insensitive copy of all named definitions and assignments.
Legacy inline role configuration will continue to work without a proxy.

The CLI will migrate legacy configuration before it writes an image proxy.
It will reuse a matching definition and preserve operator metadata.

An independent provider and model pair under `Proxies` was rejected.
That shape would duplicate model identity and metadata.

### Add one named model runtime registry

The daemon registry will map each definition name to its `ModelReference`.
The registry will create and cache one composed `IChatClient` for each used definition.
It will resolve and cache the effective capabilities for the same definition.

The current role provider will use the registry through role-to-name assignments.
This keeps the actor role API and prepares one explicit-name seam for later work.

An unknown definition or an invalid proxy capability will fail at startup.
The image proxy must accept image input and produce text output.

### Keep proxy work behind an actor service

The session actor will ask an `IImageProxyAnalyzer` for one image description.
The service will use the named registry and a fixed versioned prompt.
It will send one image, no session history, and no tools.

The service will reject an empty result.
It will neutralize its own output delimiter before it returns the text.

### Persist proxy results as session events

A session event will record these fields:

- the session-relative source path
- the proxy definition name
- the proxy model ID
- the prompt version
- the description
- the UTC timestamp in Unix milliseconds

The actor will persist this event before it calls the main model.
The snapshot will include the same records.
Recovery will rebuild the result map without a new proxy call.

The actor will request a result on demand when old history has no result.
One actor command will analyze one image at a time to keep actor state explicit.

### Select original media or derived text at assembly time

The message assembler will receive the active main modalities and the durable result map.
It will restore the original image when the main model accepts image input.
It will insert the stored description when the main model accepts text only.

The inserted text will identify the session-relative path.
It will state that the proxy output is untrusted user content.
The original media reference will remain unchanged.

### Retain image attachments when a proxy exists

The channel attachment decision will treat a configured image proxy as an image-input route.
The adapter will preserve the current attachment policy and media store path.
Its canonical attachment line will identify `via="image-proxy"`.

The adapter will not call the proxy.
The session actor remains the only owner of analysis and durable state.

## Risks / Trade-offs

- [Proxy text can contain prompt-control text] -> The wrapper labels it as untrusted and neutralizes its delimiter.
- [A proxy call adds latency] -> The actor calls it only for an image with no durable result.
- [A proxy model changes later] -> Existing results remain durable and include the original proxy identity.
- [A historical session has many images] -> The actor processes them in order and persists each result.
- [A proxy fails] -> The actor stops the turn and does not call the main model.
- [A text-only model has no proxy] -> The compatibility error from issue `#1727` remains visible.

## Migration Plan

The new property is optional.
Existing named and legacy configurations keep their current runtime behavior.
`netclaw model set image-proxy` converts a legacy model section to the named shape before it writes the assignment.

The schema accepts `Proxies` only in the named shape.
Rollback requires removal of `Models.Proxies` before an older binary reads the configuration.
Durable proxy events remain harmless session data after rollback.

## Open Questions

None.
67 changes: 67 additions & 0 deletions openspec/changes/named-image-modality-proxy/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## Why

Operators need text-only main models to use image context without a full model-router framework.
Netclaw also needs one reusable model lookup seam for future explicit model assignments.

Source PRDs: `PRD-001`, `PRD-004`, `PRD-005`, `PRD-009`
GitHub issue: `#1728`

## What Changes

- Add a runtime registry that resolves configured named model definitions by name.
- Add `Models.Proxies.Image` as an optional reference to a named model definition.
- Add CLI and TUI controls for the image proxy selection.
- Retain accepted image attachments when the main model is text-only and an image proxy exists.
- Ask the image proxy for one rich, OCR-aware description.
- Persist the description with source identity, proxy identity, and prompt version.
- Reuse the description for later text-only calls and after a daemon restart.
- Create missing descriptions on demand for historical images.
- Send the original image when the active main model supports image input.
- Fail visibly when proxy configuration or proxy analysis fails.

### In Scope

- Image input only.
- Named model definitions that already exist under `Models.Definitions`.
- One optional, fallback-only image proxy.
- Durable and lazy image analysis for session media.
- CLI, TUI, schema, runtime, persistence, and diagnostics support.

### Out of Scope

- Audio or video proxies.
- Targeted OCR or image-analysis tools.
- Subagent model selection.
- Dynamic per-turn routing, load balance, or the full design from issue `#648`.
- A duplicate provider or model configuration shape.

## Capabilities

### New Capabilities

- `named-model-runtime-registry`: Resolve any configured named model through one runtime lookup contract.
- `image-modality-proxy`: Create, persist, and reuse an image description for a text-only main model.

### Modified Capabilities

- `netclaw-model-providers`: Add a named image proxy assignment and fail-closed runtime validation.
- `netclaw-input-adapters`: Retain an accepted image when a configured image proxy can process it.
- `netclaw-config-command`: Let operators select or clear the image proxy through CLI and TUI model controls.
- `netclaw-model-capabilities`: Treat a durable proxy description as compatible text input while preserving the original media.

## Impact

The change affects model configuration, schema validation, daemon model setup, session persistence, media assembly, CLI, and TUI model controls.
The runtime registry reuses the current model client factory and named definition map.

### Security Impact

The proxy receives only an image that passed the existing attachment policy.
An invalid named reference blocks persistence or startup.
The runtime does not omit media or select another model without an explicit configuration.

### Operational Impact

The daemon needs a restart after a proxy configuration change.
Diagnostics identify the configured proxy and its named model definition.
Proxy failures produce visible session errors and do not call the main model.
Loading
Loading