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
22 changes: 15 additions & 7 deletions docs/runbooks/background-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ The `BackgroundJobManagerActor` manages job lifecycle:
- **Process isolation**: each job runs as a child `BackgroundJobExecutionActor`
that spawns the shell process with stdin closed.
- **Streaming output capture**: stdout/stderr stream line-by-line to
`~/.netclaw/jobs/{id}/output.log` *while the process runs* (stderr lines
`~/.netclaw/sessions/{session-key}/jobs/{id}/output.log` *while the process runs* (stderr lines
prefixed `[stderr]`). Each line is secret-redacted at write time. The log is
bounded by single-slot rotation: when `output.log` crosses ~5 MB it moves to
`output.1.log` (replacing any earlier rotation), so a job holds at most
~10 MB on disk and the most recent output is always in `output.log`.
- **Timeout**: a kill timer is armed **only** when the agent passes a positive
`_timeout_seconds`. Omitted means no timer — the job runs until it exits or
is reaped.
- **Definitions**: persisted to `~/.netclaw/jobs/{id}.json` for crash recovery.
- **Definitions**: persisted to `~/.netclaw/sessions/{session-key}/jobs/{id}.json` for crash recovery.

### Termination

Expand Down Expand Up @@ -75,9 +75,10 @@ job process outlives the daemon.

### Startup reconciliation

On daemon restart, the manager scans `~/.netclaw/jobs/` for definitions with
status `Running` or `Pending`. These are orphaned processes lost during the
restart — marked `Lost` with a completion timestamp, **and the owning session
On daemon restart, the manager scans each session job directory. It also scans
`~/.netclaw/jobs/` for existing definitions. Running or pending definitions are
orphaned processes. The manager marks them `Lost` with a completion timestamp.
The owning session
is notified** with the log path so the agent can relaunch. Notification volume
is bounded by design: passivated sessions have no live jobs, so only sessions
that were warm at crash time appear here.
Expand Down Expand Up @@ -117,13 +118,20 @@ category as `shell_execute`).
## Filesystem layout

```
~/.netclaw/jobs/
~/.netclaw/sessions/{session-key}/jobs/
├── abc123.json # job definition (status, command, session, timing)
└── abc123/
├── output.log # live streamed stdout + stderr (most recent)
└── output.1.log # rotated predecessor (present only after rotation)
```

Existing jobs can remain under `~/.netclaw/jobs/`. Netclaw reads and updates
these jobs at their current paths. Netclaw does not move them automatically.

The generic file tools cannot change files in a session `jobs/` directory. Use
the background job tools for job state changes. The output log files remain
readable inside the session file scope.

## Configuration

The `_timeout_seconds` metadata field on the tool call arms a per-job kill
Expand All @@ -140,7 +148,7 @@ approval policy and audience ACL as regular shell execution.

| Symptom | Check |
|---------|-------|
| Job stuck as "running" | Check `~/.netclaw/jobs/{id}.json` status; daemon may have restarted (jobs become Lost) |
| Job stuck as "running" | Check the session `jobs/{id}.json` status. Existing jobs can remain under `~/.netclaw/jobs/`. |
| No result delivered | Check daemon logs for gateway resolution failure; verify channel type matches a registered gateway |
| Job definition shows Lost | Normal after daemon restart — the owning session was notified; the pre-crash log remains readable |
| Job definition shows Reaped | Normal after the owning session went idle — the agent resubmits if still needed |
Expand Down
4 changes: 2 additions & 2 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.42.0"
version: "2.44.0"
---

# Netclaw Operations
Expand Down Expand Up @@ -102,7 +102,7 @@ view inline plus a pointer to the full output — not the whole thing:
specific range with `StartLine`/`Limit` or `grep` (`StartLine` is a 1-based line
number — line 1 is the first line). Don't `cat` a huge file through
`shell_execute` to get around it — that just spills again.
- **`background_job`** output goes to `~/.netclaw/jobs/{id}/output.log` (bounded);
- **`background_job`** output goes to `{session}/jobs/{id}/output.log` (bounded);
`check_background_job` returns a tail, and you can `file_read`/`grep` the log for the rest.

Reading a targeted range or grepping is always cheaper than re-running a command or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ audience is always allowed.
Other scheduling tools: `list_reminders`, `cancel_reminder`,
`get_reminder_history`.

Do not edit files in a session reminder or job directory with a generic file
tool. Netclaw reserves those subsystem-owned artifacts. Use the reminder and
background job tools instead. Definitions, history, and output logs remain
readable through file tools.

## Proactive channel messaging

To start a brand-new conversation on a chat channel — a `delivery_kind=channel`
Expand Down Expand Up @@ -257,8 +262,8 @@ Lifecycle:

Monitoring a running job (e.g. waiting for a dev server to come up):

- `file_read`/`grep` the output log — it streams live (secret-redacted,
rotation-bounded) at `~/.netclaw/jobs/{id}/output.log`.
- `file_read`/`grep` the output log. It streams live at
`{session}/jobs/{id}/output.log` with secret redaction and bounded rotation.
- `check_background_job(JobId: "id")` — status, elapsed time, live output tail
- Probe the service directly (e.g. curl the port) once the log shows it started.
- `check_background_job(JobId: "id", Cancel: true)` — cancel a running job.
Expand All @@ -282,7 +287,8 @@ Rules:
model server that takes minutes to respond) should run as background jobs.
- The user must approve the command before it starts running in the background.
- Maximum 5 concurrent background jobs; overflow queues FIFO.
- Job definitions persist to `~/.netclaw/jobs/{id}.json`.
- New job definitions persist to `{session}/jobs/{id}.json`.
- Existing definitions under `~/.netclaw/jobs/` stay there. Netclaw reads and updates them in place.

`check_background_job` is only available when shell execution is granted (same
`shell` grant category). It validates that the requesting session matches the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-08
136 changes: 136 additions & 0 deletions openspec/changes/session-owned-automation-artifacts/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
## Context

Netclaw stores reminder files under `~/.netclaw/schedules/reminders/`. It stores background job files under `~/.netclaw/jobs/`.

`CurrentSession` reminders return to one source session. Every background job also records one source `SessionId` and returns there.

The session directory provides the canonical file root for session artifacts. The file policy also treats that directory as a trusted root.

The reminder and job managers are daemon singletons. They coordinate concurrent work across sessions and must remain singletons.

## Goals / Non-Goals

**Goals:**

- Put session-owned reminder and job files under the source session directory.
- Keep daemon-owned reminder files under the daemon schedule directory.
- Keep the current actors, messages, scheduler payload, and tool contracts.
- Preserve all current files at their present paths.
- Let a future session-retention process remove session artifacts as one unit.

**Non-Goals:**

- Implement the future 30-day session-retention process.
- Move manager actor ownership into each session actor.
- Change reminder execution, delivery, retry, or settlement behavior.
- Change background process execution, passivation, or delivery behavior.
- Permit duplicate reminder or job IDs across sessions.
- Add a new configuration value or storage service.
- Add a background-job-specific artifact retention timer.

## Decisions

### The session directory is the physical ownership boundary

Netclaw will use these canonical paths:

```text
~/.netclaw/sessions/{session-key}/reminders/{reminder-id}.json
~/.netclaw/sessions/{session-key}/reminders/{reminder-id}.history.jsonl
~/.netclaw/sessions/{session-key}/jobs/{job-id}.json
~/.netclaw/sessions/{session-key}/jobs/{job-id}/output.log
```

`CurrentSession` reminder files will use the session paths. All background job files will use the session paths.

The reminder delivery kind defines its physical owner:

| Reminder behavior | Delivery kind | Physical owner | Definition directory |
|---|---|---|---|
| Return to the source session | `CurrentSession` | The source session | `~/.netclaw/sessions/{session-key}/reminders/` |
| Create a new session | `Channel` or `None` | The daemon | `~/.netclaw/schedules/reminders/` |

A reminder that creates a new session will remain in the daemon reminder directory. Its definition and history will not use the creator session directory.

This change moves only new `CurrentSession` reminder files. It does not move daemon-scoped reminder files.

The implementation will reuse `NetclawPaths.SessionsDirectory` and `SessionDirectoryHelper`. It will not add a parallel path configuration.

Alternative: Keep the global directories and add a separate artifact purge. This duplicates session ownership and requires two retention mechanisms.

### Manager actors remain global coordinators

The current singleton managers will retain concurrency control, reconciliation, and delivery coordination. Physical file ownership will not change actor ownership.

This decision avoids one manager per session. It also avoids a new actor protocol for cross-session capacity control.

Alternative: Move execution managers below each session actor. This adds actor lifecycle changes and splits the global concurrency limit.

### Fixed-directory scans preserve the current ID-only contracts

Each store will scan only the daemon directory and the fixed artifact directory under each session.

The stores will not add an index, a new service, or a new persistence record.

`ReminderPayload` will continue to contain only `ReminderId`. Manager commands will also keep their current ID-only forms.

This decision avoids an Akka.Reminders payload migration. It also avoids changes to protobuf messages, CLI routes, tools, and actor commands.

Alternative: Persist an absolute definition path in `ReminderPayload`. That path can become stale after a home-directory move or data restore.

Alternative: Add `SessionId` to all reminder messages. This gives stronger type context but adds unnecessary protocol changes for the current global ID model.

### The stores derive and validate every session path

A caller will provide typed ownership data, not an arbitrary file path. The store will derive the full path from `SessionId` and the artifact ID.

The store will require the result to remain under the exact source session directory. It will reject a path traversal attempt.

A loaded definition must name the same session as its containing session directory. The store will reject a mismatch and log the reason.

The current audience and boundary checks will remain unchanged. A path under another trusted root will not satisfy the session-owner check.

The stores will reject a session path that contains a symbolic link or reparse point. They will reuse `PathUtility.ContainsSymlinkSegment`.

The generic write policy will reserve the reminder and job artifact subtrees. The agent can still read definitions, history, and logs.

This rule protects the stored trust envelope from a generic file edit. The reminder and job tools remain the supported mutation surfaces.

### Current artifacts stay at their present paths

The stores will read both the daemon directories and the fixed session subdirectories. They will not move files during startup.

An update will write to the path that already owns the definition. This rule keeps active reminders and background job records stable.

Only a new `CurrentSession` reminder or background job will use a session directory. `Channel` and `None` reminders will use the daemon directory.

No scheduler replacement is necessary. The persisted reminder payload still resolves by its current reminder ID.

The stores will count only valid definitions when they detect duplicate IDs. A corrupt or owner-mismatched candidate will not hide a valid legacy definition.

A reminder update will validate its storage owner before it changes the scheduler. An invalid owner transition will leave the prior definition and schedule unchanged.

### Future session retention must respect live session automation

This change does not add session retention. A later retention design must not silently delete a session with an enabled `CurrentSession` reminder.

That design can pin the session or cancel its reminders before removal. The future OpenSpec change must select and test one policy.

Background jobs do not need a retention pin. The current passivation contract reaps them before the session becomes inactive.

## Risks / Trade-offs

- **A lookup can cost more with many sessions.** The stores scan only fixed `reminders/` and `jobs/` subdirectories.
- **A duplicate ID can make ownership ambiguous.** The stores retain global ID uniqueness and fail on duplicate definitions.
- **A direct file edit can change a subsystem-owned record.** The generic write policy reserves session automation artifacts from generic writes.
- **A future janitor can delete an active reminder.** The later retention change must define a live-reminder policy before file removal.

## Migration Plan

No file migration occurs. The new stores read current daemon paths and new session paths.

Rollback can use the prior binary for all existing files. New session-owned files require the new binary.

## Open Questions

None.
36 changes: 36 additions & 0 deletions openspec/changes/session-owned-automation-artifacts/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Why

PRD-008 requires durable scheduled work, but Netclaw stores session-owned automation artifacts in daemon-wide directories. This layout prevents a future session-retention process from deleting all artifacts for one session as a unit.

## What Changes

- Store `CurrentSession` reminder definitions and history under the source session directory.
- Store background job definitions and output logs under the source session directory.
- Keep `Channel` and `None` reminder definitions and history in the daemon-wide reminder directory.
- Keep the current reminder manager, background job manager, execution actors, delivery routes, and trust derivation.
- Persist logical session ownership and derive a validated absolute artifact path before file access.
- Restrict each session-owned path to the exact source session directory and reject path traversal.
- Read current session-owned artifacts from their present daemon-wide locations without an automatic move.
- Use the future session-retention process instead of a background-job-specific artifact timer.
- Leave the planned session-retention process outside this change.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `netclaw-session`: Define the session directory as the lifecycle boundary for session-owned automation artifacts.
- `netclaw-scheduling`: Route `CurrentSession` reminder storage by session owner while daemon-scoped reminders retain global storage.
- `reminder-execution-history`: Store reminder history beside the reminder definition under the same ownership boundary.
- `background-job-execution`: Store each job definition and output log under its source session directory without replacing the singleton execution manager.

## Impact

- **In scope:** PRD-008 storage, reminder lookup, reminder reconciliation, background job lookup, dual-location reads, and path tests.
- **Out of scope:** The planned 30-day session-retention process, a job-specific retention timer, actor ownership changes, scheduler replacement, and delivery-route changes.
- **Security:** The daemon derives paths from typed session identity. It does not accept an arbitrary path as reminder or job authority.
- **Operations:** Session removal can later remove these artifacts with the other session files. Daemon-scoped reminders remain independent of session retention.
- **Compatibility:** Current files remain in place. ID-only scheduler payloads and current reminder schedules remain valid.
Loading
Loading