INT 21h AH=31h ends the program like INT 21h AH=4Ch, but shrinks its block to DX paragraphs and keeps them, so the code in them goes on running whenever an interrupt it hooked comes: mouse drivers, keyboard enhancers, pop-up utilities and DOSKEY are made this way. The program's other memory blocks stay allocated to it, and the parent sees AH = 3 from INT 21h AH=4Dh.

The resident part goes first in the file and the installer after it, so the installer itself is not kept: DX covers the PSP and the code up to the end of the resident part, rounded up to paragraphs. In a .COM, offsets count from the start of the PSP, so the offset of the end label is already the number of bytes to keep:

Not runnable here: it would stay in memory until the box restarts.
.MODEL TINY
.CODE
ORG 100h
START: JMP INSTALL
OLD1C DD ? ; the previous INT 1Ch handler
NEW1C PROC FAR ; resident: called 18.2 times a second
PUSH ES
PUSH AX
MOV AX, 0B800h ; the colour text screen
MOV ES, AX
INC BYTE PTR ES:[158] ; the top right character changes
POP AX
POP ES
JMP CS:OLD1C ; chain to the old handler, which IRETs
NEW1C ENDP
RESEND LABEL BYTE ; everything from here is dropped
INSTALL:
MOV AX, 351Ch ; DOS: get vector 1Ch in ES:BX
INT 21h
MOV WORD PTR OLD1C, BX
MOV WORD PTR OLD1C+2, ES
MOV AX, 251Ch ; DOS: set vector 1Ch to DS:DX
MOV DX, OFFSET NEW1C
INT 21h
MOV ES, DS:[2Ch] ; free our copy of the environment
MOV AH, 49h
INT 21h
MOV DX, OFFSET RESEND + 15 ; bytes from the PSP, rounded up
MOV CL, 4
SHR DX, CL ; in paragraphs
MOV AX, 3100h ; DOS: stay resident, return code 0
INT 21h
END START

Writing the resident part

A handler runs in the middle of some other program. DS and ES are that program's, so the handler reaches its own variables through CS (CS:OLD1C), saves every register it changes, and ends with IRET or by jumping on to the handler it replaced. DOS is not reentrant: a handler for a hardware interrupt (the timer, the keyboard) must not call INT 21h while DOS is busy, which the InDOS flag from INT 21h AH=34h tells. Freeing the environment, as above, saves a few hundred bytes.

Why there is no runnable example

A resident program stays until the machine restarts. Nothing in DOS unloads it unless it was written to unload itself, and each run of an example would load one more copy and hook the vector again. Examples in this guide leave the machine as they found it, so this one is a shape to copy into a project of your own; restart the box to get rid of what it leaves behind.