Skip to content
Merged
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
16 changes: 9 additions & 7 deletions 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.107.0"
version: "2.107.1"
---

# Netclaw Operations
Expand Down Expand Up @@ -230,17 +230,19 @@ This failure does not prove that the turn exhausted its tool budget.

## Large tool output

Netclaw bounds each tool result to an inline budget
(`Session.Tuning.MaxInlineToolResultChars`, default 12,000 characters;
`shell_execute` uses 2,000). A longer result gives you its first part and its
last part, not the whole text:
Netclaw limits each inline tool result to a character budget.
`Session.Tuning.MaxInlineToolResultChars` defaults to 12,000 characters.
`shell_execute` uses 2,000 characters.
A longer result contains only its tail, with a separator before the retained text.
The shell tail retains the process exit status.

- **Each tool, also `skill_load`, `skill_read_resource`, and MCP tools**: Netclaw
keeps the full redacted result inside the current session. The last line of the
result names `tool_output_read` and a `CallId`. Use `tool_output_read` with that
`CallId` and a `Start`/`Limit` window to read the middle.
`CallId` and a `Start`/`Limit` window to read the omitted prefix or middle.
Set `Start=0` to read from the start of the retained result.
Do not request a path or rerun the source tool to read more.
If the last line says that Netclaw did not keep the full output, the middle is
If the last line says that Netclaw did not keep the full output, the omitted text is
not available: narrow the call if the tool has a bound, or read one specific
resource with `skill_read_resource`.
- **`file_read`** on a large file returns the head and steers you to read a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-09
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Context

`ShellTool` prefixes completed process output with the exit status.
The dispatcher keeps only the last 2,000 characters of a large shell result.
That tail removes the prefix on both execution paths.

Use the [engineering glossary](../../../../docs/spec/GLOSSARY.md) for shared terms.

## Goals / Non-Goals

Goals include tail-only inline output, visible exit status, deterministic tests, and accurate model guidance.
New result types, approval changes, and configuration changes are outside this fix.

## Decisions

`ShellTool` owns the authoritative process status as call-local data.
Reuse one result formatter for both completion paths.
Keep the current prefix and append the same status only when the result exceeds the existing shell inline budget.
This preserves small results and avoids a parser for result text.

The dispatcher retains its current order:

```text
execute the authorized tool
format the shell capture and process status
redact the result
keep the inline tail
write the session-scoped redacted spill
append the continuation notice
```

The dispatcher owns the inline budget and spill decision as call-local data.
The spill remains durable under the current session directory.
Actor boundaries and persistent record types do not change.

A new structured metadata contract would require more changes than this fix needs.
A larger inline budget would only move the failure threshold.

## Risks / Trade-offs

Large retained shell results contain the status at both ends.
The inline result contains the final status and needs no extra continuation call to determine the process outcome.
A failed spill still returns that status and the current explicit loss notice.
Command timeout and cancellation retain their current error results.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Why

Tail-only tool results reduce the context that models must interpret.
The new window also removes shell exit status and contradicts the current operational skill and specification.

## What Changes

- Retain only the tail of an oversized tool result.
- Preserve the shell exit status in that tail for both execution paths.
- Add deterministic tests for oversized results with zero and nonzero exit codes.
- Update the operational skill and the main specification.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `bounded-tool-output`: Require tail-only inline results and visible shell exit status after truncation.

## Impact

Source requirements: [PRD-001 FR-011](../../../../docs/prd/PRD-001-netclaw-mvp.md#fr-011-tool-access) and
[PRD-006 MCP-003](../../../../docs/prd/PRD-006-mcp-tool-integration.md#mcp-003-policy-gating).

MVP scope includes shell result text, dispatcher tests, operational guidance, and the output contract.
New tools, approval scopes, configuration properties, and unbounded capture are out of scope.

## Security and operational impact

The dispatcher retains the current redaction, default-deny authorization, and session-scoped continuation gates.
Models can read the exit status without another tool call.
Operators retain the current capture ceiling and spill behavior.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
## MODIFIED Requirements

### Requirement: Central bound + spill for every tool result

`DispatchingToolExecutor` SHALL bound every tool result to an inline budget and,
when the result exceeds that budget, return only its last budget characters plus
a separator and a continuation notice. This applies uniformly to every
tool, for the main session and for sub-agents (both run tools through the
dispatcher). The bound SHALL be applied after the dispatcher's central secret
redaction, so the inline result and any spilled file are redacted from one pass.
Individual tools SHALL NOT window, redact, or spill their results; they only bound
their own capture for memory safety and return the raw bounded result.

#### Scenario: Result under budget returned unchanged

- **WHEN** a tool returns a result at or below its inline budget
- **THEN** the dispatcher returns it unchanged
- **AND** no spill file is created

#### Scenario: Result over budget windowed to tail only

- **WHEN** a tool returns a result larger than its inline budget
- **THEN** the inline result contains only the last budget characters of the result
- **AND** a separator marks the discarded prefix
- **AND** the retained spill contains the prefix that the inline result omits

#### Scenario: Same bounding for sub-agent tool calls

- **WHEN** a sub-agent runs a tool that returns an oversized result
- **THEN** the same dispatcher bound + spill applies (sub-agents are not exempt)

### Requirement: Spill to a session-scoped file with a steer

When a result exceeds its inline budget, the dispatcher SHALL write the full redacted result to an internal file under the current session `tool-calls` directory. The dispatcher SHALL derive the file name from the sanitized call id. It SHALL return the opaque call id and a steer to `tool_output_read`. It SHALL NOT reveal the raw spill path or direct the model to shell, grep, or `file_read`. When no session directory or call id is available, the dispatcher SHALL return the inline window without a spill steer.

Concrete result shape:

```text
tool result size = 40,000 characters
inline budget = 12,000 characters
call id = call-example

model receives:
<bounded tail>

[output truncated to 12000 chars of 40000; continue with
tool_output_read using CallId='call-example' and a bounded Start/Limit
window instead of re-running]

internal storage may use:
<session>/tool-calls/call-example.txt

model never receives:
<session>/tool-calls/call-example.txt
"run grep on the saved file"
"use file_read on the saved file"
```

Counterexamples:

| Input state | Required behavior |
|---|---|
| Result fits the inline budget | Return it without a spill or continuation steer. |
| Result is oversized but the call id is absent | Return only the bounded inline window. Do not invent an id or reveal a path. |
| Call id is `../../outside` | Sanitize or reject the spill location; never write outside the session spill directory. |
| Spill write fails | Preserve the bounded inline result without claiming that continuation exists. |

#### Scenario: Spill file stays internal

- **WHEN** a result over budget is produced in a session with a directory
- **THEN** the full redacted result is written under the session `tool-calls` directory
- **AND** the inline result includes the opaque call id
- **AND** the steer names `tool_output_read`
- **AND** the steer contains no filesystem path

#### Scenario: Spilled file is redacted

- **WHEN** a result that contains a secret is spilled
- **THEN** the internal spill file has the secret redacted
- **AND** redaction occurs before the spill write

#### Scenario: Call id cannot escape the spill directory

- **WHEN** the call id contains path-traversal characters
- **THEN** the spill file stays inside the tool-calls directory
- **AND** the dispatcher reveals no raw path

#### Scenario: Missing spill identity has no false continuation

- **GIVEN** a result exceeds its inline budget
- **AND** the invocation has no usable session directory or call id
- **WHEN** the dispatcher bounds the result
- **THEN** the model receives the bounded inline window
- **AND** the result does not claim that `tool_output_read` can continue it

## ADDED Requirements

### Requirement: Shell exit status survives inline truncation

`ShellTool` SHALL include the process exit status in every successful process-completion result.
For an oversized result, it SHALL place that status at the end of its result before the dispatcher applies the inline budget.
The dispatcher SHALL retain that status in the inline tail.
This rule applies to both execution paths and to zero and nonzero exit codes.
The status describes the process outcome, not approval or tool authorization.

#### Scenario: Oversized successful command retains its exit status

- **GIVEN** an authorized shell command produces more than 2,000 result characters
- **WHEN** the command exits with code zero through either execution path
- **THEN** the inline result contains `Exit code: 0` and the output tail
- **AND** the inline result omits the output prefix
- **AND** the retained spill contains the bounded capture and its exit status

#### Scenario: Oversized failed command retains its exit status

- **GIVEN** an authorized shell command produces more than 2,000 result characters
- **WHEN** the command exits with a nonzero code through either execution path
- **THEN** the inline result contains that exact exit code and the output tail
- **AND** the inline result does not report `Exit code: 0`
- **AND** the retained spill contains the same exit status
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## 1. Preserve shell status

- [x] 1.1 Add dispatcher regression tests for oversized output with zero and nonzero exit codes on both execution paths.
- [x] 1.2 Preserve the exit status in the tail and keep small result text unchanged.
- [x] 1.3 Verify the original approval retry test and the output suites.

## 2. Align the operational contract

- [x] 2.1 Update the operational skill, increment its version, and sync the delta specification.
- [x] 2.2 Run the repository quality checks.
- [x] 2.3 Verify the implementation against the change and archive the completed artifacts.
37 changes: 31 additions & 6 deletions openspec/specs/bounded-tool-output/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Use the [Netclaw engineering glossary](../../../docs/spec/GLOSSARY.md) for cross
### Requirement: Central bound + spill for every tool result

`DispatchingToolExecutor` SHALL bound every tool result to an inline budget and,
when the result exceeds that budget, return a head+tail window of the budget plus
a steer pointing the model at the full output. This applies uniformly to every
when the result exceeds that budget, return only its last budget characters plus
a separator and a continuation notice. This applies uniformly to every
tool, for the main session and for sub-agents (both run tools through the
dispatcher). The bound SHALL be applied after the dispatcher's central secret
redaction, so the inline result and any spilled file are redacted from one pass.
Expand All @@ -27,11 +27,12 @@ their own capture for memory safety and return the raw bounded result.
- **THEN** the dispatcher returns it unchanged
- **AND** no spill file is created

#### Scenario: Result over budget windowed to head and tail
#### Scenario: Result over budget windowed to tail only

- **WHEN** a tool returns a result larger than its inline budget
- **THEN** the inline result contains the head and the tail of the result within
the budget, with the discarded middle marked
- **THEN** the inline result contains only the last budget characters of the result
- **AND** a separator marks the discarded prefix
- **AND** the retained spill contains the prefix that the inline result omits

#### Scenario: Same bounding for sub-agent tool calls

Expand Down Expand Up @@ -72,7 +73,7 @@ inline budget = 12,000 characters
call id = call-example

model receives:
<bounded head and tail>
<bounded tail>

[output truncated to 12000 chars of 40000; continue with
tool_output_read using CallId='call-example' and a bounded Start/Limit
Expand Down Expand Up @@ -166,3 +167,27 @@ return more than the configured limit.
- **WHEN** `tool_output_read` executes
- **THEN** the outcome is `not_found`
- **AND** the bounded result suggests a narrower source call

### Requirement: Shell exit status survives inline truncation

`ShellTool` SHALL include the process exit status in every successful process-completion result.
For an oversized result, it SHALL place that status at the end of its result before the dispatcher applies the inline budget.
The dispatcher SHALL retain that status in the inline tail.
This rule applies to both execution paths and to zero and nonzero exit codes.
The status describes the process outcome, not approval or tool authorization.

#### Scenario: Oversized successful command retains its exit status

- **GIVEN** an authorized shell command produces more than 2,000 result characters
- **WHEN** the command exits with code zero through either execution path
- **THEN** the inline result contains `Exit code: 0` and the output tail
- **AND** the inline result omits the output prefix
- **AND** the retained spill contains the bounded capture and its exit status

#### Scenario: Oversized failed command retains its exit status

- **GIVEN** an authorized shell command produces more than 2,000 result characters
- **WHEN** the command exits with a nonzero code through either execution path
- **THEN** the inline result contains that exact exit code and the output tail
- **AND** the inline result does not report `Exit code: 0`
- **AND** the retained spill contains the same exit status
Loading
Loading