A live forecast of the context window, drawn above the prompt.

English | 中文
DeepSeek Harness (dsh) is an open-source agent harness developed by DeepSeek AI.
It is built on an everything-is-a-plugin architecture and powered by Cordis, whose design is described in _A Programming Paradigm for Spatiotemporal Composability_.
Documentation: https://deepseek-harness.github.io/deepseek-harness/
DeepSeek Harness is in developer preview and iterating rapidly. THERE WILL BE COMPATIBILITY-BREAKING CHANGES.
Review the safety notice before running the project.
npmInstall Node.js, then run:
npx @deepseek-ai/dsh web
The command starts the Web UI at http://127.0.0.1:3080 by default and opens it in the default browser for a local launch. An SSH launch only prints the host URL because the SSH client or editor owns the local forwarded address. Pass --no-open to run the server without opening a browser. See Web UI guide.
To run from a repository checkout:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
pnpm run build prepares the repository artifacts. pnpm dsh web uses those built artifacts without rebuilding.
dsh-plugin topic to your plugin repository for discoverability.See CONTRIBUTING.md.
Start with the development guide and architecture documentation.
pnpm run dev:web builds, serves, and rebuilds client bundles on source edits in one terminal, and make help lists the matching Make targets for Web and Desktop; the guide's application commands section owns the full table.
For agents, follow AGENTS.md.
@misc{deepseek-harness2026,
title={DeepSeek Harness: Everything is a Plugin},
author={DeepSeek-AI},
year={2026},
publisher={GitHub},
howpublished={\url{https://github.com/deepseek-ai/deepseek-harness}},
}
Third-party dependencies and their licenses are disclosed in THIRD_PARTY_NOTICES.md.
hooks/token-weather.mjs 88 lines1// Token Weather, from "Getting started with Claude Code mods"
2// (https://claude.dev/blog/getting-started-with-claude-code-mods/, Anthropic, 2026-10-01).
3// The published module, unchanged.
4
5// Token Weather: a live forecast of the context window, above the prompt.
6
7const HISTORY = 12;
8const BARS = "▁▂▃▄▅▆▇█";
9const FORECAST = [
10 { upTo: 25, icon: "☀", word: "Clear", color: "yellow" },
11 { upTo: 50, icon: "☁", word: "Cloudy", color: "cyan" },
12 { upTo: 75, icon: "☂", word: "Showers", color: "blue" },
13 { upTo: 90, icon: "☇", word: "Storm", color: "magenta" },
14 { upTo: Infinity, icon: "↯", word: "Compact soon", color: "red" },
15];
16
17// Held by the host, so the history survives a hot reload of this file.
18const readings = { plugin: "token-weather", key: "readings" };
19
20export function register(on) {
21 on("session.start", async ($, e, next) => {
22 const result = await next(e);
23 await takeReading($);
24 return result;
25 });
26
27 on("turn.complete", async ($, e, next) => {
28 const result = await next(e);
29 if (!e.agentId) {
30 await takeReading($); // main-loop turns only, not subagents
31 }
32 return result;
33 });
34
35 on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
36 const { value: history = [] } = await $.state.get(readings);
37 if (e.props.hasSurvey || history.length === 0) {
38 return next(e);
39 }
40 const { Box, Text } = $.ui.resolve(e);
41 return band(Box, Text, history, e.props.bodyColumns);
42 });
43}
44
45async function takeReading($) {
46 const { context } = await $.session.usage();
47 if (!context?.window) return;
48 const tokens = context.tokens ?? 0;
49 const percent = context.percent ?? Math.round((tokens / context.window) * 100);
50 const { value: history = [] } = await $.state.get(readings);
51 await $.state.set(readings, [...history, { tokens, window: context.window, percent }].slice(-HISTORY));
52}
53
54function band(Box, Text, history, columns) {
55 const now = history[history.length - 1];
56 const f = FORECAST.find((b) => now.percent < b.upTo);
57 const parts = [
58 Text({ color: f.color, bold: true, children: `${f.icon} ${f.word}` }),
59 Text({ children: ` ${now.percent}% of context` }),
60 Text({ dimColor: true, children: ` ${short(now.tokens)} / ${short(now.window)}` }),
61 ];
62 if (columns >= 60) {
63 parts.push(Text({ dimColor: true, children: " last turns " }));
64 parts.push(Text({ color: f.color, children: sparkline(history) }));
65 if (history.length > 1) {
66 parts.push(Text({ dimColor: true, children: trend(history) }));
67 }
68 }
69 return Box({ flexDirection: "row", paddingX: 1, children: parts });
70}
71
72function sparkline(history) {
73 const top = Math.max(...history.map((r) => r.tokens), 1);
74 return history.map((r) => BARS[Math.floor((r.tokens / top) * (BARS.length - 1))]).join("");
75}
76
77function trend(history) {
78 const delta = history[history.length - 1].tokens - history[history.length - 2].tokens;
79 if (delta === 0) return " steady";
80 return delta > 0 ? ` ▲ +${short(delta)} last turn` : ` ▼ ${short(-delta)} last turn`;
81}
82
83function short(n) {
84 if (n >= 1_000_000) return `${+(n / 1_000_000).toFixed(1)}M`;
85 if (n >= 1_000) return `${+(n / 1_000).toFixed(1)}k`;
86 return String(n);
87}
88types/index.d.ts 11 lines1// Token Weather's type contract, from https://claude.dev/blog/getting-started-with-claude-code-mods/
2// (Anthropic, 2026-10-01). The published file, unchanged.
3
4export type TokenWeatherReading = { tokens: number; window: number; percent: number }
5
6declare module 'claude-code' {
7 interface PluginState {
8 'token-weather': { readings: TokenWeatherReading[] }
9 }
10}
11