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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@

- [x] 4.1 Update working-context and consumer guidance to state that `{session_dir}` is private scratch and that cleanup is not yet automatic.
- [x] 4.2 Run strict OpenSpec validation for this change and the parent structured-approval change.
- [ ] 4.3 Run focused policy, actor, shell-environment, and eval assertion tests on Linux; run native Windows coverage for path and PowerShell behavior.
- [x] 4.3 Run focused policy, actor, shell-environment, and eval assertion tests on Linux; run native Windows coverage for path and PowerShell behavior.
- [ ] 4.4 Run the full required build, tests, headers, formatting, Slopwatch, and eval gates for every changed system-guidance artifact.
- [x] 4.5 Record automated session-directory cleanup as explicit future scope without implementing deletion in this pull request.
87 changes: 86 additions & 1 deletion openspec/specs/netclaw-scheduling/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,11 @@ when listing tasks.
### Requirement: Failure handling and guardrails

The reminder manager SHALL store consecutive failures in each reminder
definition. A successful execution SHALL reset the count.
definition. An execution failure and a scheduling failure SHALL increment the
same count. A successful execution SHALL reset the count. A successful
reschedule alone SHALL NOT reset the count. The post-fire reschedule of a cron
reminder runs before that occurrence executes. A reset at that point erases a
pending execution-failure count.

The manager SHALL disable a reminder when the count reaches
`FailurePauseThreshold`. The disabled definition SHALL remain available for
Expand All @@ -356,6 +360,28 @@ is acceptable.
Each execution SHALL have a one-hour absolute limit. A known timeout SHALL
count as a failed attempt.

A scheduling failure is a failure to compute or install the next occurrence at
an unattended reschedule site. The two unattended sites are the post-fire
reschedule of a recurring reminder and the startup reconcile restore loop.
Causes include an unresolvable `CRON_TZ` time zone, a cron expression with no
future occurrence, and an uninitialized reminder client.

At an unattended site, the manager SHALL do all of the following:

- increment the reminder's `ConsecutiveFailures` count;
- emit an `OperationalAlert.ReminderScheduleFailed` alert at Warning severity;
- disable the reminder when the count reaches `FailurePauseThreshold`, emit an
`OperationalAlert.ReminderAutoDisabled` alert at Critical severity, and post a
channel notice.

The manager SHALL NOT evaluate an unresolvable time zone in UTC. The manager
SHALL NOT skip a failed reschedule without a report. A fire at the wrong time is
worse than a missed fire.

The create path and the update path (`set_reminder`) SHALL return a scheduling
error to the caller. Those paths SHALL NOT emit a scheduling-failure alert,
because the caller already sees the error.

#### Scenario: Consecutive failures disable a reminder

- **GIVEN** a reminder has one fewer failure than `FailurePauseThreshold`
Expand Down Expand Up @@ -384,6 +410,65 @@ count as a failed attempt.
- **THEN** the execution is cancelled and reported as a failure
- **AND** the failure is counted toward `FailurePauseThreshold`

#### Scenario: Post-fire reschedule failure is surfaced

- **GIVEN** a recurring reminder fires
- **AND** the manager cannot compute its next occurrence
- **WHEN** the manager attempts the post-fire reschedule
- **THEN** the manager increments the reminder's `ConsecutiveFailures` count
- **AND** the manager emits an `OperationalAlert.ReminderScheduleFailed` alert
- **AND** the current occurrence still executes

#### Scenario: Reconcile schedule failure is surfaced

- **GIVEN** an enabled reminder whose next occurrence is not computable at startup
- **WHEN** the reconcile restore loop attempts to reschedule it
- **THEN** the manager increments the reminder's `ConsecutiveFailures` count
- **AND** the manager emits an `OperationalAlert.ReminderScheduleFailed` alert
- **AND** the manager does not skip the reminder without a report

#### Scenario: Consecutive scheduling failures disable a reminder

- **GIVEN** a reminder has one fewer scheduling failure than `FailurePauseThreshold`
- **WHEN** the manager reports the next scheduling failure
- **THEN** the manager disables the reminder
- **AND** the manager emits an `OperationalAlert.ReminderAutoDisabled` alert at
Critical severity
- **AND** the manager posts a channel notice

#### Scenario: A successful execution resets scheduling failures

- **GIVEN** a reminder has two consecutive scheduling failures
- **AND** its schedule recovers, so the reminder fires again
- **WHEN** that occurrence executes successfully
- **THEN** the manager saves a zero failure count

#### Scenario: A successful reschedule alone does not reset the count

- **GIVEN** a cron reminder has a failure count above zero
- **WHEN** the post-fire reschedule of an occurrence succeeds
- **THEN** the manager keeps the current failure count
- **AND** only a later successful execution resets it

#### Scenario: An unresolvable time zone never falls back to UTC

- **GIVEN** a cron reminder with an unresolvable `CRON_TZ` time zone
- **WHEN** the manager attempts a reschedule at an unattended site
- **THEN** the manager schedules no occurrence
- **AND** the manager does not evaluate the reminder in UTC
- **AND** the manager reports the failure through the count and a
`ReminderScheduleFailed` alert

#### Scenario: One bad startup does not disable many reminders

- **GIVEN** many enabled reminders whose schedules all fail once at startup
- **AND** `FailurePauseThreshold` is greater than one
- **WHEN** the reconcile restore loop runs
- **THEN** the manager increments each affected count by one
- **AND** the manager disables no reminder because of one startup failure
- **AND** the manager emits an `OperationalAlert.ReminderScheduleFailed` alert
for each affected reminder

### Requirement: Execution history CLI command

The CLI SHALL provide a `netclaw reminder history <id>` subcommand that
Expand Down
85 changes: 67 additions & 18 deletions openspec/specs/session-cwd/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,38 +176,46 @@ the approval policy depends on.

### Requirement: Shell tool failure-path hint for cwd outside safe spaces

`ShellTool` SHALL include a one-line hint in the tool result returned
to the model when a call is denied because its cwd is outside both
`session_dir` and `project_dir`. The hint SHALL suggest
`set_working_directory <path>` with the path that triggered the denial,
in a format recognizable to the agent so it can self-correct without a
roundtrip through the user.
`ShellTool` SHALL include a one-line remediation hint in the tool result returned to the model when a call is denied because its cwd is outside both `session_dir` and `project_dir` and a safe correction is available.

The hint SHALL only be emitted when the denial reason is "cwd outside
safe spaces" and `set_working_directory` is in the audience's tool
exposure list. The hint SHALL NOT be emitted for hard-deny-list refusals
or for `ToolPathPolicy` denials (those have different remediation paths).
For a non-temp cwd, the hint SHALL suggest `set_working_directory <path>` only when that tool is exposed and the same filesystem policy used by `set_working_directory` accepts the exact path without substitution. For a Personal cwd equal to the captured platform temporary root, the hint SHALL instead identify the exact session directory as private scratch and SHALL NOT suggest declaring the platform temporary root. Team and Public shell calls retain their existing earlier denial boundary, and Public results SHALL retain existing path redaction.

#### Scenario: Denial in foreign tree includes set_working_directory hint
The hint SHALL NOT be emitted for hard-deny-list refusals, `ToolPathPolicy` denials, an unavailable session scratch path, or a foreign non-temp cwd that `set_working_directory` would reject.

#### Scenario: Denial in declarable foreign tree includes set_working_directory hint

- **GIVEN** a Personal session with `project_dir` not set
- **AND** the shared directory policy accepts `~/repos/bar/`
- **WHEN** the agent invokes `shell_execute` with cwd `~/repos/bar/`
- **AND** the user denies the resulting prompt
- **THEN** the tool result includes a hint pointing at
`set_working_directory ~/repos/bar/`
- **THEN** the tool result includes a hint pointing at `set_working_directory ~/repos/bar/`

#### Scenario: Denied platform-temp retry retains scratch recommendation

- **GIVEN** an agent received the session-scratch correction for the platform temporary root
- **AND** it repeated the original call unchanged to request ordinary approval
- **WHEN** the user denies that approval
- **THEN** the tool result identifies the exact session directory as private scratch
- **AND** it does not suggest `set_working_directory` for the platform temporary root

#### Scenario: Undeclarable foreign tree has no project declaration hint

- **GIVEN** a non-temp cwd is outside the roots accepted by `set_working_directory`
- **WHEN** the user denies the resulting shell prompt
- **THEN** the tool result does not suggest declaring that cwd

#### Scenario: Hint is not emitted for hard-deny refusals

- **GIVEN** a hard-deny-list block on the command
- **WHEN** `shell_execute` returns the deny error
- **THEN** the result does NOT include a `set_working_directory` hint
- **THEN** the result does NOT include a working-directory remediation hint

#### Scenario: Hint is not emitted when set_working_directory is unavailable
#### Scenario: Hint is not emitted when remediation tools are unavailable

- **GIVEN** a Public session where `set_working_directory` is not in
the tool exposure list
- **GIVEN** a Public session where `set_working_directory` is not exposed
- **AND** no private session-scratch correction is available
- **WHEN** a shell call is denied for cwd-outside-safe-space
- **THEN** the result does NOT include a `set_working_directory` hint
- **THEN** the result does NOT include a working-directory remediation hint

### Requirement: set_working_directory expands the approval safe space

Expand Down Expand Up @@ -348,3 +356,44 @@ The context SHALL be derived from the child run's existing bound session scope.
- **THEN** its project scope and project instructions update through the existing contract
- **AND** its bound `session_dir` remains unchanged

### Requirement: Session directory is the private shell scratch location

The system SHALL identify the existing per-session directory as the private scratch location for disposable shell artifacts. Personal and Team model-visible working-context and correction text SHALL provide its absolute path when the agent needs an alternative to the platform temporary root. Public contexts SHALL retain existing path redaction and SHALL NOT receive the private absolute session path. The system SHALL NOT create a second scratch directory, silently substitute the path, or imply that session-directory cleanup occurs as part of this behavior.

#### Scenario: Shell without project scope defaults to session scratch

- **GIVEN** a session has no declared project directory
- **AND** its session directory is `/home/user/.netclaw/sessions/example`
- **WHEN** the agent invokes `shell_execute` without an explicit working directory
- **THEN** the shell working directory is `/home/user/.netclaw/sessions/example`
- **AND** the working context identifies that directory as session scratch

#### Scenario: Scratch recommendation uses the existing session directory

- **GIVEN** a correction recommends private scratch
- **WHEN** the correction is rendered for the agent
- **THEN** it names the exact existing session directory
- **AND** it does not name a newly created `scratch` child directory

#### Scenario: Public context does not receive private scratch path

- **GIVEN** a Public parent agent or subagent
- **WHEN** it evaluates a platform-temp shell call
- **THEN** it does not receive the private session-directory path
- **AND** existing Public path-redaction behavior remains

#### Scenario: Personal and Team headless context nudges scratch use

- **GIVEN** a Personal or Team headless session
- **WHEN** its working context is assembled
- **THEN** the context identifies the exact session directory as private scratch for disposable artifacts
- **AND** it states that an explicitly required platform-temp path must be preserved
- **AND** it does not imply that approval prompts or automatic cleanup exist

#### Scenario: No cleanup is implied

- **GIVEN** an agent writes a disposable artifact under the session directory
- **WHEN** the current session ends
- **THEN** this capability does not delete or schedule deletion of that artifact
- **AND** retention remains unchanged until a separate cleanup capability is specified

Loading
Loading