Skip to content
Closed
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
7 changes: 7 additions & 0 deletions docs/spec/SPEC-011-daemon-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ The daemon writes its PID to `~/.netclaw/netclaw.pid` for lifecycle management.
shutdown. The daemon handles SIGTERM by draining active sessions and stopping
the actor system cleanly.

During drain, a session can stop a tool task that waits only for durable
approval prompts. The session waits for the tool task to stop before it
acknowledges drain. Its journal retains the prompts and completed sibling
results. An approval after restart resumes the original turn under its
recorded authority. Active tools, accepted buffered input, and incomplete
results keep the current bounded drain path.

`netclaw daemon status` checks the PID file and verifies the process is alive.
Reports: running/stopped, PID, uptime, port, number of active sessions.

Expand Down
7 changes: 6 additions & 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.74.7"
version: "2.74.8"
---

# Netclaw Operations
Expand Down Expand Up @@ -425,6 +425,11 @@ failed or was superseded); the session then posts a visible "approval prompt has
expired" notice rather than silently dropping the click. If a user reports a
stale button, ask them to re-issue the request.

During a graceful stop, the session can stop a tool task that waits only for
journaled approval prompts. The session waits for that task to stop before it
acknowledges drain. The original requester can still approve after restart.
An active tool or accepted buffered input keeps the current bounded drain path.

**Why you may not see a prompt at all.** If the user invokes a read-only verb
(say `grep`) with a path argument under a tree the operator has previously
trusted, the safe-verb short-circuit applies and there is no prompt. This
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/stop-durable-approval-waits/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-18
62 changes: 62 additions & 0 deletions openspec/changes/stop-durable-approval-waits/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## Context

See [the proposal](proposal.md) for the delay. `LlmSessionActor` keeps the active tool task alive while `SessionToolExecutionPipeline` waits for an approval. A `ToolApprovalRequested` event records a durable parent-session prompt. `ToolCallRecorded` records a completed sibling result. The pipeline now has no explicit stop acknowledgment.

The session actor owns the live task and the drain decision. The session journal owns approval and result state. The actor buffer and deferred approval response are actor-local data. The [engineering glossary](../../../docs/spec/GLOSSARY.md) defines shared terms.

## Goals / Non-Goals

**Goals:**

- Stop a tool task after all unfinished calls have durable approval records.
- Acknowledge drain only after that task stops.
- Keep approved tool work safe for cold recovery under its original turn authority.

**Non-Goals:**

- Resume a canceled model call or accepted input automatically.
- Shorten the global stop limit before other active states have safe paths.

## Decisions

### The actor checks each call at the journal boundary

The active batch tracker knows expected call IDs and applied result IDs. The approval state knows each pending call and whether its prompt is durable. The actor uses both sets after event callbacks apply. It does not trust a prompt that has only entered the actor mailbox.

The actor can use the fast path only when each expected call has an applied result or an unresolved durable approval. At least one call must await approval. The actor excludes accepted buffered input and a deferred approval response. This rule rejects active siblings and non-durable child approvals.

### The tool task supplies the stop signal

The actor retains the task returned by the current tool pipeline. After eligibility, the actor cancels that task's token. A task completion signal returns to the actor mailbox. The actor then passivates and acknowledges drain. The actor does not treat token cancellation alone as proof that work stopped.

The pipeline can report cancellation as a failed batch. The actor suppresses only the cancellation that belongs to this verified drain attempt. An unrelated failure keeps the existing failure path. A stale task signal cannot complete a newer batch.

### The journal remains the recovery source

The actor adds no new persisted event in this slice. The existing approval and result events restore the parked batch. A recovered approval response uses its recorded `TurnContextRecord`. An incomplete legacy context cannot grant broader authority.

### Schematic sequence

```text
graceful stop -> session actor marks drain requested
approval/result journal callback -> actor checks every call
if all unfinished calls await durable approval and local buffers are empty:
actor cancels the current tool task
tool task stops -> actor receives the stop signal
actor passivates -> drain ack
else:
current bounded drain path remains
```

The sequence omits ordinary policy checks and journal callbacks.

## Risks / Trade-offs

- A sibling can finish while the actor checks eligibility. The actor uses applied result events and mailbox order to keep the result before drain.
- A tool can ignore cancellation. The actor then gives no early acknowledgment. The global stop limit still applies.
- A resolved approval can race with drain. The actor excludes an unfinished resolved call and keeps the bounded path.
- An accepted buffered message has no durable admission record today. The actor excludes that state until the later input-admission slice.

## Migration Plan

This change needs no journal migration or configuration update. A rollback removes the fast path. Journaled approvals keep their current recovery behavior.
33 changes: 33 additions & 0 deletions openspec/changes/stop-durable-approval-waits/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Why

Source PRD: [PRD-001 FR-016](../../../docs/prd/PRD-001-netclaw-mvp.md#fr-016-config-change-restart-coordination).

A session can wait for a tool approval until the daemon reaches its 190-second stop limit. The approval already has a journal record. Netclaw can stop that wait after the tool task stops.

## What Changes

- Let a session stop promptly when every unfinished tool call waits on a durable approval.
- Require each completed sibling tool call to have a journaled result before the session stops.
- Require the tool task to stop before the actor acknowledges drain.
- Keep the current bounded drain path for model calls, active tools, unresolved results, accepted buffered input, and deferred approval responses.
- Preserve the original approval and its turn authority after cold recovery.

This slice does not add automatic session wakeups, new input admission records, or a shorter global stop limit.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `session-resume`: A graceful daemon stop can passivate a session that waits only for durable tool approvals.

## Impact

The change affects `LlmSessionActor`, its tool task boundary, the session-resume contract, and actor tests. It adds no public API or configuration property.

### Security and operational impact

The actor cannot stop before every unfinished call has a journaled approval. It cannot replay a call with an uncertain effect. The daemon retains its current timeout for other states.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## ADDED Requirements

### Requirement: Graceful stop of durable approval waits

During a graceful daemon stop, a session SHALL finish drain when every unfinished tool call waits on a durable approval. The session SHALL wait for the tool task to stop before it acknowledges drain. The journal SHALL retain approval requests and completed tool results for cold recovery.

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

#### Scenario: One durable approval stops promptly

- **GIVEN** a session has one unfinished tool call with a journaled approval request
- **WHEN** the daemon requests a graceful drain
- **THEN** the session stops the tool task and acknowledges drain without an approval response
- **AND** the original requester can approve the call after cold recovery
- **AND** the recovered turn keeps its original authority

#### Scenario: Completed sibling retains its result

- **GIVEN** one tool result has a journal record and another tool call waits on a journaled approval
- **WHEN** the daemon requests a graceful drain
- **THEN** the session acknowledges drain after the tool task stops
- **AND** recovery does not execute the completed sibling again

#### Scenario: Active sibling prevents the fast path

- **GIVEN** one tool call waits on a journaled approval and another tool call has no journaled result or approval
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path
- **AND** the current bounded drain path remains in effect

#### Scenario: Accepted buffered input prevents the fast path

- **GIVEN** a session has accepted user input in its actor buffer
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path

#### Scenario: Non-durable or resolved approval prevents the fast path

- **GIVEN** an unfinished call has a non-durable approval or a resolved approval without a result
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path

#### Scenario: Deferred approval response prevents the fast path

- **GIVEN** the session has a deferred approval response
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path
17 changes: 17 additions & 0 deletions openspec/changes/stop-durable-approval-waits/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## 1. Actor proof

- [x] 1.1 Add an actor test for a journaled approval during drain. Run it against the baseline and confirm that the drain ack does not arrive.
- [x] 1.2 Add the tool task stop signal and the durable approval gate. Verify that the actor test receives the ack after task stop.
- [x] 1.3 Cold-recover the session, approve the parked call, and verify one execution under the original trust context.

## 2. Negative boundaries

- [x] 2.1 Add a sibling tool gate and verify that an active sibling prevents an early drain ack.
- [x] 2.2 Verify that an unresolved result, non-durable approval, accepted buffer, or deferred response prevents the fast path.
- [x] 2.3 Run the focused actor tests and verify no duplicate tool action or incomplete journal result.

## 3. Contract and gates

- [x] 3.1 Update `SPEC-011` and the `netclaw-operations` system skill. Verify that both describe the approval-only drain rule.
- [x] 3.2 Run the behavioral eval suite for the skill change and verify its result.
- [x] 3.3 Run the actor test project, Slopwatch, header verification, OpenSpec validation, and `git diff --check`.
46 changes: 46 additions & 0 deletions openspec/specs/session-resume/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,52 @@ truth for in-flight approval state.
- **THEN** recovery SHALL succeed with an empty pending-interaction set
- **AND** SHALL NOT fail or error on the missing field

### Requirement: Graceful stop of durable approval waits

During a graceful daemon stop, a session SHALL finish drain when every unfinished tool call waits on a durable approval. The session SHALL wait for the tool task to stop before it acknowledges drain. The journal SHALL retain approval requests and completed tool results for cold recovery.

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

#### Scenario: One durable approval stops promptly

- **GIVEN** a session has one unfinished tool call with a journaled approval request
- **WHEN** the daemon requests a graceful drain
- **THEN** the session stops the tool task and acknowledges drain without an approval response
- **AND** the original requester can approve the call after cold recovery
- **AND** the recovered turn keeps its original authority

#### Scenario: Completed sibling retains its result

- **GIVEN** one tool result has a journal record and another tool call waits on a journaled approval
- **WHEN** the daemon requests a graceful drain
- **THEN** the session acknowledges drain after the tool task stops
- **AND** recovery does not execute the completed sibling again

#### Scenario: Active sibling prevents the fast path

- **GIVEN** one tool call waits on a journaled approval and another tool call has no journaled result or approval
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path
- **AND** the current bounded drain path remains in effect

#### Scenario: Accepted buffered input prevents the fast path

- **GIVEN** a session has accepted user input in its actor buffer
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path

#### Scenario: Non-durable or resolved approval prevents the fast path

- **GIVEN** an unfinished call has a non-durable approval or a resolved approval without a result
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path

#### Scenario: Deferred approval response prevents the fast path

- **GIVEN** the session has a deferred approval response
- **WHEN** the daemon requests a graceful drain
- **THEN** the session does not acknowledge drain through the approval fast path

### Requirement: Recovered pending approvals restore turn context

When a session recovers pending tool approvals from the journal, it SHALL also restore the original turn context for each pending approval. The restored context SHALL include the requester, audience, boundary, channel type, approval capability, principal classification, provenance, and adopted-context safety state needed to resume the original request faithfully.
Expand Down
Loading
Loading