Host-resident cron engine with a web jobs board and a timer_agent tool: fires real agent sessions on schedule, continuing a pinned session for context or starting fresh ones in a chosen project workspace.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:LouisHaoL/dsh-timer-agent
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
中文 | English
A DeepSeek Harness (DSH) Web GUI plugin: a host-resident scheduled-jobs engine built after studying the cron system of NousResearch/hermes-agent and following its "timer ↔ agent coordination" design. It is live the moment the dsh web service starts — it keeps firing with the GUI page closed.

What it does
At each cron due point (5-field cron) it executes your prompt through a real agent session:
- Pin an existing session → every run continues that conversation with full context (hermes-cron continuity semantics)
- Set a project workdir → every run starts a fresh session inside that project (its AGENTS.md loads automatically)
- Leave both blank → every run starts a new conversation in the default workspace
Manage jobs right from any conversation with the timer_agent tool (create / list / update / pause / resume / remove / run); the web GUI sidebar「定时任务」panel manages the same jobs — one ledger, three doorways (tool / WebUI / file).
Two job kinds
- AI Agent job (default): each fire drives a real agent session executing your prompt; consumes API quota
- Command job: each fire spawns
command + argsyou specify (optional workdir and timeout) — no AI, no quota; exit code plus the tail (≤16k chars) of stdout/stderr lands on the execution record. Great for self-contained scripts (downloads, exports, credential renewal)
[!WARNING] Command jobs execute arbitrary commands on your machine with your current user privileges — no sandbox, no allowlist. Anyone who can create/edit jobs (you, any agent session able to call the
timer_agenttool, any local process that can reach the loopback API) can schedule arbitrary programs. Only schedule commands you have reviewed and that are safe to run unattended; do not expose this plugin to untrusted environments; a tamperedjobs.jsonledger is equivalent to arbitrary local code execution. Use with care.
Architecture (isomorphic to hermes-agent cron)
┌─ dsh web host process ────────────────────────────┐
│ 60s ticker (resident; runs with GUI closed) │
│ ├─ HostJobStore ~/.dsh/timer-agent/jobs.json │
│ │ (atomic writes, degrades safe)│
│ ├─ TimerRunner at-most-once: roll nextRunAt │
│ │ forward BEFORE firing; skip │
│ │ while running; missed = skipped│
│ │ ├─ agents.resume (pinned session) │
│ │ └─ agents.create + workspaceRegistry │
│ │ (fresh session attached to project) │
│ ├─ timer_agent tool (model-facing) │
│ └─ /api/dsh-timer-agent/* routes (loopback-only)│
└────────────────────────┬──────────────────────────┘
│ HTTP polling mirror (5s)
┌─ Browser half (thin)───┴──────────────────────────┐
│ Sidebar entry + jobs board (React) │
│ Collapsible project/session tree · cron presets │
│ · execution history │
└───────────────────────────────────────────────────┘
Run settlement rides session/event (turn/end's reason.kind); failure reasons land verbatim in the ledger.
Features
- Scheduling: 5-field cron (min hour day month weekday;
*,*/n,a-b, comma lists) + preset dropdown (daily 09:00 / hourly / every 10 min / Mondays 09:00) on both create and edit surfaces - Three schedule modes: a dropdown on the create form — Cron / fixed interval / one-time (right after the schedule toggle, disabled while unchecked); fixed-interval grids stack whole periods from the last trigger and never drift across restarts
- One-shot jobs: a single run instant (defaults to now + 1h) that fires once and auto-archives — success, failure, and a manual "run now" all consume the shot; ideal for finish-and-forget scripts and reminders
- Editable next run: interval and one-shot jobs allow hand-editing the next run instant; cron stays strictly expression-driven (server-refused)
- Pause / resume semantics: pausing keeps the stored next run; resuming recomputes from the REAL last execution and skips missed occurrences straight to the next future slot
- Target tree picker: collapsible per-project groups, each with a "new session" leaf plus the project's existing sessions (most recent first); picking a session pins that conversation
- Jobs board: list (title/status/next-run/run count), search, detail view (cron editor, execution history, jump to session transcript, run now, reset, delete)
- Model tool:
timer_agentin any conversation creates and manages the same jobs - System-prompt injection: the host half registers a
plugin:timer-agentannouncement section so agents know the capability - Safety: API routes are loopback + same-origin only (same fence as dsh-ssh)
v0.8.0 changes (dsh 0.2.0-rc.2 compatibility)
Adapts to dsh 0.2.0-rc.2 (up from v0.7.0's dsh 0.1.5-rc.2 across two minors; two breaking changes):
- Settings service rework: the host removed
SettingsProvider.installSectionruntime registration in favor ofSettingsForms— plugin config is now injected declaratively by the cordis loader from the exportedConfigschema (values live on the plugin row of the profile patch; an edit reloads the plugin fiber), and the client-side config form is generated by SettingsForms automatically. The plugin drops itsinstallSectionregistration and thesettingsinject;enabled/announceToAgentsemantics are unchanged - Schemastery fork switch: dsh 0.2.0 pins
@deepseek-ai/schemasteryto~3.18.4(3.18.3/4 introduced theMode/SchemaOutputtype system), and the merged same-nameSchemadeclarations now clash with plainschemastery, failing typecheck. The plugin switches to the fork, the same source the host uses (runtime behavior unchanged — the host calls schemas duck-typed) - Everything else verified unchanged item by item: the service inject names (four after dropping
settings), webServer routes, systemPrompt.section, tools.register,session/event/turn/end, the client-sideslots/sessionsinjects, and the/plugins/<id>/client.jsserving path; thedsh.compatibility.dshReleasesdeclaration now reads0.2.0-rc.2 - peer/dev dependencies aligned to the dsh 0.2.0-rc.2 pairing (cordis ~4.0.4, all
@deepseek-ai/dsh-*at 0.2.0-rc.2) - Additive host extensions, consumed nowhere and harmless here: tools gained the
canceldecision andprojectContentprojection; system-prompt gained theinterpolateoption plusTOOL_COMPUTER_USE/MCP_SERVERSsection orders; ui-settings gained thesettings.launcherslot andconfigForms(renamed fromsettingsScope)
Backward compatibility: the job ledger format is unchanged — a v0.7.0-or-older ~/.dsh/timer-agent/jobs.json works as-is. Live-verified on isolated DSH_HOMEs booting dsh web 0.2.0-rc.2 and 0.1.5-rc.2: the plugin mounts, /api/dsh-timer-agent/jobs answers 200, and the ledger sees zero side effects on both. For older dsh use v0.7.0.
v0.7.0 changes (dsh 0.1.5-rc.2 compatibility)
Adapts to dsh 0.1.5-rc.2 (up from v0.6.0's dsh 0.1.2-rc.1; the host plugin surface stayed stable across the four minors, with exactly two breaking changes):
agents.cancel(cause): the host replaced the free-text cause with a stable intent enum (user/parent/hook/disposed). The plugin's timeout cancellation now speaks{ kind: 'hook', reason: … }(automated cancel with a reason, no user present)- Cold-read split:
sessionPersistence.inspectwas removed. The pinned-session preset rebuild now reads throughsessionQuery.readSession(full raw event log;agent-preset/selectedstill wins) and falls back tosessionPersistence.statfor the header - Everything else was verified unchanged (the five service inject names, webServer routes, systemPrompt.section, tools.register,
session/event/turn/end, agentDefaultModel/llm/agentPresets, the followup message shape); thedsh.compatibility.dshReleasesdeclaration now reads0.1.5-rc.2 - peer/dev dependencies aligned to the dsh 0.1.5-rc.2 pairing (cordis ^4.0.2); the plugin keeps plain
schemastery(the host calls schemas duck-typed, the fork changes nothing)
Backward compatibility: the job ledger format is unchanged — a v0.6.0-or-older ~/.dsh/timer-agent/jobs.json works as-is. Verified on dsh ≥ 0.1.5 only; for older dsh use v0.6.0.
v0.5.0 changes & backward compatibility
Changes
- The execution model now depends entirely on the persisted next-run instant (
nextRunAt): cron / interval only compute it; a due instant fires - New one-shot jobs (
runAton create /run_aton thetimer_agenttool): one fire, then the job archives; a manual run consumes the shot too - Pausing no longer clears the next-run instant; resuming recomputes from the real last execution (
executionslaststartedAt, falling back tolastTriggeredAt) without replaying missed runs - Interval / one-shot next-run instants are hand-editable (PATCH
nextRunAt/ toolnext_run_at); cron refuses manual instants by design - Skip-once is meaningless for a one-shot (skipping == not running): the UI hides it and the server refuses
Backward compatibility
- No ledger migration needed — upgrade in place: cron / fixed-interval / paused rows from v0.4.0 and earlier survive untouched; blank legacy rows are still dropped (as before) and a corrupted ledger still degrades instead of crashing
- Old code paths could never write a one-shot-shaped row (blank cron AND no interval WITH an instant), so no pre-existing job can be misclassified as one-shot and auto-archived
- Only caveat: after upgrading, rolling back to v0.4.0 makes the old validator drop one-shot schedules (those jobs become unscheduled); cron / interval jobs are unaffected
Install
dsh plugin --profile web add link:<absolute path to this directory>
Then restart dsh web; the sidebar「定时任务」entry confirms it is live (browser-side changes need a Ctrl+F5 force refresh).
Build
pnpm install
pnpm run build # lib/index.js (host) + lib/client.js (browser, CSS inlined)
pnpm run typecheck
pnpm test # 49 behavioral E2E checks (fake host faces, no dsh runtime)
pnpm run smoke-test # static structure smoke (23 checks)
The E2E suite covers: cron parsing and next-run computation (local-time semantics), ledger atomic writes and corrupted-file degradation, at-most-once due firing, pinned-session resume, turn/end success/failure settlement (failure reason lands verbatim), skip-while-running, disabled schedules never firing, the manual-run fast path, workdir propagation, every timer_agent tool action (incl. invalid-argument rejection), and HTTP route CRUD + run + loopback/same-origin fencing + 400s on bad input.
Mapping to hermes-agent cron
| hermes-agent | this plugin |
|---|---|
| in-process 60s ticker in gateway | 60s ticker in the dsh web host process |
| fire → new AIAgent(platform=cron) session | agents.create/resume real dsh sessions |
| ~/.hermes/cron/jobs.json ledger | ~/.dsh/timer-agent/jobs.json (atomic writes) |
| at-most-once (advance next_run_at first) | roll nextRunAt forward before firing |
| claim dedup + heartbeat | skip-while-running + 5s manual-run fast path |
| deliver backfill / platform pinning | pinned session / workdir new session / default space |
| cron_hint → notepad → script prompt assembly | self-contained prompt (no human present) |
| turn/end reason.kind=error settlement | same-shape session/event settlement |
Compatibility, dependencies & permissions
Compatibility (declared in package.json):
- DSH:
0.2.0-rc.2(exactcompatibleviadsh.compatibility.dshReleases; other releases unverified =unknown) - Node.js:
>=22(engines.node; the repo's tests rely on Node 22+ type-stripping) - OS: verified on Windows 10/11; macOS/Linux unverified
Dependencies:
- Single runtime dependency
@deepseek-ai/schemastery(config schema validation, the host-sourced fork), installed with the package; no install/postinstall/prepare lifecycle scripts @deepseek-ai/*andreactare peerDependencies provided by the dsh host; the plugin never disables, replaces, or re-installs official components- No install-time builds, downloads, or remote installs; the
lib/build output is committed alongside the source
Permissions (the four signals visible to static source scanning — all required by plugin features):
- Files: atomic read/write of the jobs ledger
~/.dsh/timer-agent/jobs.json(store); never touches dsh core directories or other profiles - Commands:
command-kind jobs spawn the command the job author configured (optional workdir cwd, inherits hostprocess.env);prompt-kind jobs execute through the dsh session API and never open a shell - Network: localhost only — the web GUI calls same-origin
/api/dsh-timer-agent/*routes and the host talks to the local dsh service via the dsh client; no external services - Credentials: spawned children inherit host
process.env(dsh credentials reach the CLI via env vars); the plugin itself never reads, logs, or persists credentials or secrets
Failure bounds: a stopped service process fires nothing (missed means missed); a corrupted ledger degrades to an empty table with the original file backed up; a mid-run due slot skips; manual and ticker triggers share one at-most-once channel and never double-execute.
Source anchor: v0.8.0 released at tag v0.8.0.
Known limits
- Firing depends on the
dsh webservice process being alive (a stopped service fires nothing; after restart only already-rolled-forward due jobs run — missed means missed) - A job that is mid-run at its due point skips that slot and waits for the next cron match
- Executions consume API quota; scheduled runs have no human present — prompts must be self-contained and must not ask questions
Credits
- NousResearch/hermes-agent — the cron architecture blueprint
- The dsh-web-ui collection (dsh-ssh / dsh-client-ui-task-board) — engineering precedents for host services, route registration, sidebar injection, and the preset dropdown
License
Links
More in this category
loopx-project/loopx#dsh-loopx-plugin★ 6226
LoopX, a provider-neutral, local-first state kernel and control plane for long-horizon agents: keeps Goal, Todo, gate, evidence, quota, recovery, and handoff state above DeepSeek Harness, while the plugin bootstraps the CLI and skills, admits bounded same-session continuation, and adds a loopback GoalBar for the exact bound loop.
Q00/ouroboros#integrations/dsh-plugin★ 6191
Config-only bundle that mounts Ouroboros through the DSH MCP client, exposing 36 interview, Seed, execution, evaluation, and evolution workflow tools in DSH.
chuspeeism/dashi-taskboard#deepseek-harness★ 3316
Embeds the active installed Codex Taskboard runtime in the DeepSeek Harness sidebar, using its launcher runtime descriptor instead of a fixed port.
NanmiCoder/dsh-agent-teams★ 1995
AgentTeams multi-agent teams.
EthanYoQ/AI-Novel-Writer#dsh-ai-novel-writer★ 1377
Installs a dedicated AI novel-writing preset and workbench: revisioned local project assets, a compact side drawer, and native approval-gated single-file changes.
tong-io/tongflow#dsh-tongflow★ 1042
TongFlow film-crew studio for image, voice, music and video production: the agent writes per-asset TongFlow workflow files (.tongflow.json) that run through TongFlow plugins, with an embedded workflow canvas, a shot/character/take project layout and a manga-drama template; sessions starting with @tongflow open the Studio view.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.