Back to Openviking

Resource Watches

docs/en/api/15-watches.md

0.4.127.9 KB
Original Source

Resource Watches

The Watch API manages periodic resource checks, pausing, resuming, and manual triggers.

API Reference

Watch Management

List, inspect, update, and trigger watch tasks created via add_resource with watch_interval > 0. The control plane is mirrored across REST (/api/v1/watches), the ov task watch CLI subcommand group, and a minimum-closure MCP surface (list_watches / cancel_watch) for agents.

1. API Implementation Overview

This control plane wraps the WatchManager primitives without changing any server-side behavior. Every endpoint and CLI command resolves the target task by either its task_id (path) or its to_uri (query). The two keys are interchangeable; if both are supplied they must refer to the same task, otherwise the request is rejected with 400.

Operations:

  • List (GET /api/v1/watches) — returns {tasks, total}; pass ?active_only=true to filter; pass ?to_uri=... to collapse to a single-task lookup
  • Show (GET /api/v1/watches/{task_id}) — inspect one task; optional ?to_uri= performs a cross-key sanity check
  • Update (PATCH /api/v1/watches/{task_id} or PATCH /api/v1/watches?to_uri=...) — partial update of watch_interval, is_active, reason, instruction. is_active is orthogonal to watch_interval: flip is_active to pause/resume without losing the configured cadence.
  • Delete (DELETE /api/v1/watches/{task_id} or DELETE /api/v1/watches?to_uri=...)
  • Trigger (POST /api/v1/watches/{task_id}/trigger or POST /api/v1/watches/trigger?to_uri=...) — fire-and-forget refresh; returns immediately while the underlying re-ingest runs in the background

Code Entry Points:

  • openviking/server/routers/watches.py — REST router for /api/v1/watches
  • crates/ov_cli/src/commands/watch.rsov task watch CLI subcommand group
  • openviking/server/mcp_endpoint.py — MCP list_watches / cancel_watch tools and the watch_interval / to parameters on add_resource
  • openviking/resource/watch_manager.py:WatchManager — task persistence and scheduling primitives

2. Interface and Parameter Description

For every single-task endpoint the path {task_id} can be replaced with a ?to_uri= query argument. The CLI <key> argument is auto-classified: any value starting with viking:// routes to the by-URI path, anything else is treated as a task ID (other URI schemes such as http:// are rejected locally to avoid silent 404s).

PATCH /watches body (all fields optional; at least one is required)

FieldTypeDescription
watch_intervalfloatNew cadence in minutes. Must be > 0; use is_active=false to pause without losing the cadence.
is_activeboolToggle activation without losing the cadence (pause / resume).
reasonstringUpdate the recorded reason for the watch.
instructionstringUpdate the semantic processing instruction.

Unrecognized fields are rejected with HTTP 400 and INVALID_ARGUMENT (extra="forbid" on the request model). Fields left unset preserve their current values.

3. Usage Examples

HTTP API

bash
# List active watch tasks (drop ?active_only to include paused ones)
curl -s "http://localhost:1933/api/v1/watches?active_only=true" \
  -H "X-API-Key: your-key"

# Pause a watch without losing its cadence
curl -X PATCH "http://localhost:1933/api/v1/watches/<task_id>" \
  -H "X-API-Key: your-key" -H "Content-Type: application/json" \
  -d '{"is_active": false}'

# Trigger an immediate refresh (fire-and-forget; returns before the re-ingest finishes)
curl -X POST "http://localhost:1933/api/v1/watches/<task_id>/trigger" \
  -H "X-API-Key: your-key"

# Resolve by URI instead of task ID
curl -X DELETE "http://localhost:1933/api/v1/watches?to_uri=viking://resources/guide.md" \
  -H "X-API-Key: your-key"

Python SDK

python
watches = client.list_watches(active_only=True)
client.update_watch(to_uri="viking://resources/guide.md", is_active=False)
client.trigger_watch(to_uri="viking://resources/guide.md")
client.delete_watch(to_uri="viking://resources/guide.md")

TypeScript SDK

typescript
const watches = await client.listWatches({ activeOnly: true });
await client.updateWatch(
  { toUri: "viking://resources/guide.md" },
  { isActive: false },
);
await client.triggerWatch({ toUri: "viking://resources/guide.md" });
await client.deleteWatch({ toUri: "viking://resources/guide.md" });

Go SDK

go
watches, err := client.ListWatches(ctx, &openviking.ListWatchesOptions{
    ActiveOnly: true,
})
updated, err := client.UpdateWatch(ctx, openviking.UpdateWatchOptions{
    ToURI:    "viking://resources/guide.md",
    IsActive: openviking.Bool(false),
})
triggered, err := client.TriggerWatch(ctx, openviking.WatchRef{
    ToURI: "viking://resources/guide.md",
})
deleted, err := client.DeleteWatch(ctx, openviking.WatchRef{
    ToURI: "viking://resources/guide.md",
})
_, _, _, _ = watches, updated, triggered, deleted

CLI

The following examples use the ov task watch subcommands:

bash
# List active watches (drop --active-only to include paused ones)
ov task watch ls --active-only

# Inspect a single watch (key may be either a viking:// URI or a task_id)
ov task watch show viking://resources/guide.md

# Pause / resume without losing the cadence
ov task watch pause viking://resources/guide.md
ov task watch resume viking://resources/guide.md

# Update the cadence (or any combination of --active / --reason / --instruction)
ov task watch update viking://resources/guide.md --interval 30

# Trigger an immediate fire-and-forget refresh
ov task watch trigger viking://resources/guide.md

# Remove a watch task entirely
ov task watch rm viking://resources/guide.md

Response

Listing tasks returns:

json
{
  "status": "ok",
  "result": {
    "tasks": [
      {
        "task_id": "7f02e980-8df9-4f27-a570-4d8428cbed8a",
        "path": "https://example.com/guide.md",
        "to_uri": "viking://resources/guide.md",
        "parent_uri": "viking://resources",
        "reason": "keep documentation current",
        "instruction": "",
        "watch_interval": 30,
        "build_index": true,
        "summarize": false,
        "processor_kwargs": {},
        "created_at": "2026-07-24T10:00:00",
        "last_execution_time": null,
        "next_execution_time": "2026-07-24T10:30:00",
        "is_active": true,
        "account_id": "default",
        "user_id": "default",
        "original_role": "user"
      }
    ],
    "total": 1
  }
}

Getting one task and a successful update return the same task object directly in result. Delete and trigger return:

json
{
  "status": "ok",
  "result": {
    "task_id": "7f02e980-8df9-4f27-a570-4d8428cbed8a",
    "to_uri": "viking://resources/guide.md",
    "deleted": true
  }
}
json
{
  "status": "ok",
  "result": {
    "task_id": "7f02e980-8df9-4f27-a570-4d8428cbed8a",
    "to_uri": "viking://resources/guide.md",
    "scheduled": true
  }
}

scheduled=true only confirms that background execution was scheduled. It does not mean re-ingestion has completed; read the task again and inspect last_execution_time.

MCP (agent control plane — minimum closure only)

text
list_watches()                                            # one line per task; URIs only, no task_ids surfaced
cancel_watch(to_uri="viking://resources/guide.md")        # idempotent removal by URI

Pause / resume / trigger / update are intentionally not exposed via MCP — those power-user operations live on the CLI/REST surface to keep the agent system prompt compact. Creating a watch or changing its cadence from the agent side still goes through add_resource with watch_interval; pass to explicitly or let the system bind to the root_uri returned by this import.