Files
deepseek-harness/packages/context/tmux-context
creatixchu b7034e4a26 feat(web): declare the remaining context forms on every shipped producer
Four values complete the vocabulary, so the opaque body is reached only by
producers that genuinely promise no shape.

`snapshot` — current state a later snapshot supersedes. system-prompt now
exposes `renderContextSections()`, the named contributions
`renderContextSnapshot()` already joins for the model, so the body attributes
each part to the subsystem that produced it instead of re-splitting joined
prose. The runtime snapshot, time-context, and tmux-context declare it.

`notice` — a one-off account of what just happened, declared by tool-tasks,
goal state changes, tool-goal wrap-up, plan-mode switches, and
repeat-tool-guard. Its `summary` rides the COLLAPSED row: these five are the
majority of shipped producers and none of them needs expanding to be read.
The task summary bounds itself because its inputs are unbounded caller text.

`relay` — a message another agent addressed to this one; both subagent
sources declare it and the body names the sender above what it said.

`recall` — material lifted from another session's log. session-reference
needed no new field: its references already record retained and omitted
counts and the truncation flag, which the body shows first, because recalled
context is bounded on the way in.

`ContextFormed` is now discriminated by `form`, so a producer cannot declare
a shape without the facts that shape is presented from — a notice without its
summary, or a snapshot without its sections, fails to compile.

Only the two hook bridges stay opaque, by design: their content is whatever
an external program printed, so no shape can be promised for it. Unknown
kinds and unreadable records land there too.
2026-08-05 17:11:48 +08:00
..

@deepseek-ai/dsh-tmux-context

English | 中文

Opt-in durable context naming the tmux session, window, and pane this agent process runs in, plus the window's pane-tree layout. Sampled once per turn during model-request preparation. The shipped TUI mounts it; dsh-agent-spine-demo and the Web/headless surfaces do not. Decision record: the tmux-context Agent Note.

Config

- id: tmux-context
  name: '@deepseek-ai/dsh-tmux-context'
  config:
    refreshIntervalMs: 60000 # optional; omit or set to 0 to inject on every changed turn

refreshIntervalMs must be a non-negative safe integer. Omission or 0 injects whenever the tmux state changed since the last injection. A positive value additionally suppresses injections that fall within that many milliseconds of the latest one.

How it reads tmux

The plugin prepends an agent/step listener that runs only on the first step of each turn. When due, it runs one read-only command through the ctx.bash executor seam:

[ -n "$TMUX_PANE" ] || exit 1
self_tty=$(ps -o tty= -p <pid> | tr -d ' ')
pane_tty=$(tmux display-message -t "$TMUX_PANE" -p '#{pane_tty}') || exit 1
[ "$pane_tty" = "/dev/$self_tty" ] || exit 1
exec tmux display-message -t "$TMUX_PANE" -p '<format>'

$TMUX_PANE alone is insufficient: a terminal launched from a tmux shell (a VS Code integrated terminal, a desktop launcher) inherits $TMUX and $TMUX_PANE from that ancestor, so the variables are present even though the process does not live in that pane. The command therefore also compares the pane's #{pane_tty} against this process's own controlling terminal (ps -o tty= for its pid): a genuine pane owns this process's tty, while an inherited environment names some other pane's tty. Running through ctx.bash applies the deployment's sandbox and policy; the plugin owns no subprocess code. When ctx.bash is absent, the process is not in a real tmux pane ($TMUX_PANE unset, or the tty does not match ⇒ nonzero exit), or the reading is malformed, the attempt is a no-op, never an error. The location is optional, so an executor rejection — a policy refusal from resolve() or an infrastructure failure from run() — is contained and logged as a warning rather than failing the turn.

State is pulled on every eligible turn — a moved, renamed, or re-laid-out pane is picked up without any tmux hook or background process. The plugin re-injects only when the rendered tmux state differs from its last injection, so an unchanged location adds nothing.

Timing semantics

When an injection is due, the plugin appends one injected user/message through agent.inject() before step/start, with source { kind: 'plugin', plugin: 'tmux-context' }. Change suppression and interval scheduling scan the raw durable session events for the latest injection of this source, so the schedule survives compaction and resumed processes without process-local cache state; sessions schedule independently. The reading records a request-preparation attempt, not a committed step; because the listener runs first, its append may remain when a later pre-step listener cancels or fails the attempt (the log is append-only and the plugin performs no rollback).

Model Experience

Preparation-time tmux location

What the model sees

On each turn whose tmux state changed, one source-tagged context message with the three lines below. <window-layout> is tmux's compact pane-tree description; pane and window pixel sizes are intentionally excluded, and the contents of sibling panes are never captured.

Changed-turn reading
tmux location (turn <turn>):
session <session>, window <index> "<name>", pane <index> <pane-id>
window active=<0|1>, pane active=<0|1>, layout <window-layout>

Token effect

Each two-line reading accumulates until compaction shadows it. Unchanged locations and interval suppression add nothing.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Known Limitations and Deferred Work

  • First step only — a pane moved or resized mid-turn is reflected on the next turn, not between steps.
  • Own location only — the plugin never captures the visible text of sibling panes.
  • Layout, not size — pane/window pixel dimensions are omitted; only the layout tree and active flags are reported.
  • Tab-delimited fields — a tmux window name containing the literal two-character sequence \t would mis-split the reading and be skipped as malformed; ordinary names are unaffected.
  • tty-based pane detection — the process is considered "in tmux" only when its controlling terminal matches $TMUX_PANE's #{pane_tty}. This deliberately excludes terminals that inherited $TMUX/$TMUX_PANE from a tmux ancestor (e.g. a VS Code integrated terminal). ps -o tty= is POSIX; the check is a no-op wherever it or #{pane_tty} is unavailable.