Agent trajectory governance and anomaly diagnosis: rebuilds flat session logs into multi-branch trajectory trees, detects loop deadlock, invalid retry, and goal drift, alerts with cost attribution, one-click interrupt and breakpoint fork via official APIs, independent GUI tab.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:dfycaly98931680/dsh-trajectory-governance
GitHub-sourced plugins run build scripts on your machine at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Agent trajectory governance & anomaly diagnosis for DeepSeek Harness (dsh).
Rebuilds the flat session/event log into a structured, multi-branch trajectory
tree, keeps observation-layer snapshots, and runs three temporal anomaly
strategies (loop deadlock / invalid retry / goal drift) with results mounted on
tree nodes and surfaced in an independent GUI tab.
Zero kernel modification · consume-only event subscription · observation-layer snapshots (no control-plane rollback) · independent SQLite storage · never overwrites the official Trajectory view.
Why
The official Trajectory page is a flat raw-event log. For long-running agent tasks (dozens of turns, subagents, forks) you cannot see:
- which tool call belongs to which sub-task / subagent / fork branch,
- where the agent started drifting from the original goal,
- whether it is looping in place burning tokens (deadlock / invalid retry).
This plugin is an upper observation & reasoning layer: it consumes committed events and derives structure, snapshots, and diagnoses — it never steers, intercepts, or rolls back the agent. Both views coexist.
| Aspect | Official Trajectory | dsh-trajectory-governance |
|---|---|---|
| View | flat raw event log | structured multi-branch trajectory tree |
| Lineage | none | parent/child + fork/subagent branches (derived implicitly) |
| Snapshots | no | observation-layer snapshots (nodeId + context hash + branchId) |
| Diagnosis | no | loop_deadlock / invalid_retry / goal_drift with confidence + suggestions |
| Storage | host storage | plugin-private SQLite (~/.dsh-trajectory-governance/) |
| Behavior | — | consume-only; never schedules, forks, or rewinds the agent |
Architecture
dsh web profile
├─ @deepseek-ai/dsh-base (official core)
└─ dsh-trajectory-governance (this bundle)
│
│ ctx.on('session/event' | 'session/created' | 'session/disposed'
│ | 'subagent/start' | 'subagent/end') ← emit-mode, post-commit, read-only
▼
┌─ ingest ───────────────┐ ┌─ tree ─────────────────┐
│ normalize (scalars) │──▶│ cross-session trajectory │
│ sqlite (events/sessions)│ │ tree + branch attach │
└────────────────────────┘ └─────────────────────────┘
┌─ snapshot ─────────────┐ ┌─ diagnose ──────────────┐
│ index-only snapshots │ │ 3 strategies (async) │
│ LRU/TTL prune, branches │ │ results → anomalies DB │
└────────────────────────┘ └─────────────────────────┘
│
▼
┌─ api (host) ────────────┐ ┌─ client (browser) ─────┐
│ /trajectory-governance/ │◀──│ independent Tab: │
│ api/* (JSON, same-origin)│ │ conversation.view slot, │
└─────────────────────────┘ │ id 'trajectory-…' │
└─────────────────────────┘
Data flow: session/event (durable, post-commit) → normalized + persisted →
tree/snapshot/diagnosis services → JSON API → the GUI tab. The diagnosis engine
runs asynchronously on an interval (incremental watermark + tail overlap) and
never blocks the agent loop.
Install
Requires dsh >= 0.1.0-rc.6. Two ways:
Bundle (published / git):
dsh plugin --profile web add github:dfycaly98931680/dsh-trajectory-governance#<commit-sha>
A git install fetches sources, so the package ships a prepare build; pnpm
requires an explicit allow-list for it (add the printed key to the profile's
pnpm-workspace.yaml, then re-run):
allowBuilds:
dsh-trajectory-governance: true
Alternatively install a built tarball (pnpm pack) — no build permission needed.
Dev overlay (no pnpm):
dsh web --patch C:\path\to\dsh-plugin\overlay.dev.yml
Then restart the web app. You should see [trajectory-governance] loaded; storage=... sessions=N events=M anomalies=K in the logs.
Use
- Run any agent task in the GUI (ideally a long one).
- Open the 轨迹治理 / Trajectory Governance tab (a separate
conversation.viewentry — the official Trajectory tab is untouched). - Pick a session: the tree renders with branch badges (subagent/fork), snapshot marks (★), and red-highlighted anomaly ranges (⚠).
- Click a node for the raw event + mounted diagnosis report (type, confidence, severity, description, suggestion).
- Use the snapshot bar to capture checkpoints at any node, list them, and jump back; delete stale branches via the API/CLI.
CLI inspection utilities (against the plugin's own SQLite):
node scripts/inspect.mjs [dbPath] # sessions/events overview
node scripts/tree.mjs <sessionId> [dbPath] # trajectory tree JSON
node scripts/snapshot.mjs <dbPath> <cmd> [...] # list|create|context|delete|prune
Configuration
The plugin row accepts config (all optional; deep-merged over defaults):
- insert:
- id: trajectory-governance
name: dsh-trajectory-governance
config:
storage:
path: C:/data/trajectory.db # default ~/.dsh-trajectory-governance/trajectory.db
diagnosis:
strategyA:
enabled: true
windowSize: 5
similarityThreshold: 0.85
resultSimilarityThreshold: 0.8
strategyB:
enabled: true
minRounds: 3
strategyC:
enabled: true
sampleEveryNRounds: 5
similarityThreshold: 0.5
consecutiveSamples: 2
embedder: lexical # 'lexical' built-in; 'llm' is the extension seam
analyzer:
enabled: true
intervalMs: 10000
alerts:
enabled: true
minConfidence: 0.8
cooldownMs: 600000
channels:
desktop: true # browser Notification (client side)
cordisEvent: true # 'trajectory/anomaly' bus event for the ecosystem
webhook:
url: "" # Feishu/DingTalk/Slack-style text webhook
actions:
onLoopDeadlock: notify # notify | suggest-stop(一键中断按钮) | auto-stop
onInvalidRetry: notify
onGoalDrift: notify
cost:
enabled: true
tokenPricePerM: 0.28 # adjust to your provider pricing
estimateWhenUsageMissing: true
Full schema: DIAGNOSIS_CONFIG_SCHEMA in src/diagnose/config.ts.
Alerts & stop-loss (v0.2)
When a NEW anomaly is detected (confidence ≥ alerts.minConfidence), the plugin:
- emits
trajectory/anomalyon the Cordis bus (ecosystem notification plugins can subscribe) and POSTs a text webhook if configured — the message is in stop-loss language: type, confidence, and how much tokens/money/time the range already consumed (from officialassistant/message.usage); - the GUI tab polls
/api/alerts, shows a desktop notification (browser Notification API, permission button in the bar), and pins abnormal sessions on top of the dropdown sorted by wasted tokens; - offers a one-click interrupt (
POST /api/actions/interrupt) which calls the officialagent.cancelcapability — never a monkey-patch. Default tier isnotify;auto-stop(loop deadlock only, confidence ≥ 0.9) can be configured for unattended setups.
Breakpoints & waste reports (v0.3)
Breakpoint exit — the legitimate lightweight "rollback". Instead of a
control-plane rewind (already covered by dsh-turn-rewind),
this plugin offers a diagnosis-driven restart: at any snapshot / anomaly
start / selected node, "branch a new session here" resolves the target down
to the nearest stable turn/end boundary and calls the official
ctx.sessions.fork(source, boundary) — a fresh child session seeded at that
point. It never touches workspace files, keeps the observation-layer stance,
and turns "you wasted money here" into a one-click action.
Waste attribution report — not another usage-stats dashboard (that niche is saturated): it answers the attribution question nobody else does, "which sessions wasted tokens/money, on which anomaly types":
curl http://127.0.0.1:3080/trajectory-governance/api/report/waste # all sessions (Markdown + JSON)
curl "http://127.0.0.1:3080/trajectory-governance/api/report/waste?sessionId=<id>" # one session detail
Both are also reachable from the tab ("浪费报告" / "本会话报告" buttons).
Quick demo (reproduce loop deadlock & goal drift)
Instant (no waiting for a real loop): seed a synthetic session into the
live store, refresh the tab, pick demo-session:
node scripts/demo-seed.mjs # writes into the default live DB
You should see a tree with red-highlighted loop_deadlock (5 identical tool
calls) and a severe goal_drift range, plus a snapshot mark (★).
Real agent:
- Give the agent a long, open-ended refactor prompt (e.g. "重构这个项目为 TypeScript 并补单元测试,先读代码再动手").
- Let it run until it starts repeating the same tool call (same file, same
content) with no visible progress — Strategy A flags
loop_deadlockon the trigger node. - Ask a follow-up in a different domain ("顺便帮我看看怎么做番茄意面") — after
N sampled turns Strategy C flags
goal_drift(mild/moderate/severe) against the original baseline.
Verify: the tree tab highlights the ranges in red; scripts/inspect.mjs shows the
anomaly counts; scripts/tree.mjs <sessionId> dumps the mounted anomalyInfo.
Screenshots
TODO — add after the first live run: tree tab overview / anomaly card / snapshot list. (Placeholders reserved here.)
Testing
npm install
npm run build && npm test # 46 tests: normalize/store/subscriber/tree/snapshot/diagnose/api
Boundaries (important)
- Zero kernel modification — no fork, no monkey-patch, no host storage access.
- Consume-only events —
session/eventetc. are emit-mode, post-commit, fire-and-forget listeners; payloads are never mutated. - Observation-layer snapshots — a snapshot is an index (nodeId + context hash
- branchId); context is rebuilt from the event store. This plugin never forks, resumes, or rewinds the agent.
- Independent Tab — registered with its own slot id; the official Trajectory
page (id
trajectory) is never overwritten. - Preview API —
SESSION_FORMAT_VERSION = 0, no compatibility implied. The event vocabulary is centralized insrc/core/event-catalog.ts; unknown types honor the envelope'signorablemarker.
Repository layout
src/
index.ts # Cordis plugin entry (name/apply)
core/ # types + 44-type event catalog
ingest/ # normalize + session-event subscriber
persistence/ # node:sqlite store (events/sessions/snapshots/anomalies)
tree/ # trajectory tree builder + queries
snapshot/ # observation snapshots + branch manager
diagnose/ # similarity, config, 3 strategies, engine, mount, runtime
api/ # host JSON API (/trajectory-governance/api/*)
client/ # independent GUI tab (conversation.view, id 'trajectory-governance')
tests/ # node:test suites
scripts/ # inspect / tree / snapshot / smoke
cordis.patch.yml # official bundle load row
plugin.manifest.yaml # self-describing metadata (loader uses cordis.patch.yml)
License
MIT — see LICENSE. Part of the awesome-dsh-plugin
ecosystem. Topic: dsh-plugin.
Links
More in this category
liustack/modlens★ 1261
Vision bridge for text-only models: paste an image, get structured JSON evidence (OCR, layout, semantics).
Anionex/dsh-vision-toolkit★ 323
Vision tasks for text-only models: intent-aware image Q&A, long-screenshot OCR, UI reproduction, grounding, and pixel diff.
zhaoolee/notes★ 138
Export DSH conversations as Smartisan Notes-style PNGs, or create and update Markdown notes in a configured account-scoped workspace.
Lum1104/dsh-browser★ 89
Chrome sidebar extension that lets DSH operate your browser directly, no vision capabilities required.
liustack/modsearch★ 88
Web search bridge for text-only agents: ask the web or X, get structured JSON evidence (search, fetch, citations).
taxueseek/argo★ 69
Search built for agents: multilingual coverage across web, academic, code, shopping, finance, news, and encyclopedias.