dfycaly98931680/dsh-trajectory-governance
Agent trajectory governance & anomaly diagnosis plugin for DeepSeek Harness (dsh): multi-branch trajectory trees, loop-deadlock / invalid-retry / goal-drift detection, cost attribution, alerts, one-click interrupt & breakpoint fork, independent GUI tab. Zero kernel modification.
Listed
4
Tools
Bundle verified
Preview
What it does
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.
Best for
- Operators investigating long-running DSH sessions with subagents, forks, or many tool calls.
- Teams that need structured trajectory trees and diagnosis of loop deadlock, invalid retries, or goal drift.
- Users who want anomaly alerts with cost attribution plus explicit interrupt and breakpoint-fork controls.
Not ideal for
- Users expecting control-plane rollback or automatic steering; snapshots are observational and diagnosis runs asynchronously.
- Short, simple sessions where a structured multi-branch view adds little over the official raw event log.
- Deployments below dsh 0.1.0-rc.6 or requiring a stable session-format compatibility guarantee.
README
dsh-trajectory-governance
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.
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:dfycaly98931680/dsh-trajectory-governance in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.