SLOPSHOPPER

time-sense

Tells the model the time after a long gap, and how long its own turns really take. A few dozen tokens, only when useful.

prompt
v0.1.0MITupdated 2026-10-17u6vu06Ou/time-sense
A shopper browsing a rack in a slop shop
README

time-sense

A Claude Code mod that gives the agent a sense of time, for a few dozen tokens.

The problem. An agent has no clock. Come back after six hours and it acts as if no time passed. Ask how long a task will take and it answers in human workdays ("1–2 days") for work it finishes in ten minutes.

Situations it is meant for. (What changes is what the model sees; how it reacts is up to the model. These are the cases I wrote it for, not measured results.)

  • "I'm going to bed, let's continue tomorrow." You come back the next morning in the same conversation. Without a clock the agent can't tell whether 2 minutes or 9 hours passed. time-sense shows it this conversation's last message 9h05m ago.
  • Resuming an old conversation. You --resume a session from three weeks ago. The agent sees last message 21d3h ago, a cue that plans and "what's running" may be stale.
  • "How long will this take?" Agents tend to answer in human workdays. time-sense shows the agent its own measured turn times, so it has real numbers to estimate from.

What it does.

  1. Clock after a gap. On the first prompt of a session, and whenever you return after 20+ minutes of silence, it attaches one hidden line the model reads: [time] now 2026-10-17 Sat 21:40 · this conversation's last message 6h12m ago The gap is tracked per conversation, so resuming an old session shows that session's own gap. Back-to-back chatting adds nothing.
  2. Real pace. It records how long each main-loop turn actually took (wall clock). Once it has 5+ samples, the first prompt of each session also gets: [pace] your last 40 turns took median 4m, longest 38m of real time. Estimate durations in these terms, not human workdays.

Cost: ~20 tokens per gap, ~30 tokens once per session. No model calls, no system-prompt changes (so prompt caching is untouched). If the mod fails, the prompt goes through unchanged.

Install

/plugin install time-sense --marketplace u6vu06Ou/time-sense

(Answer y to add the marketplace, then pick the user scope so it works in every project. Pick project scope if you want it in one project only.)

Notes

  • Uses your machine's local time zone.
  • Durations are stored with the mod's own store (last 40 turns); nothing leaves your machine.

繁中簡介

讓 Claude Code 有時間感:隔很久回來時,偷偷告訴 AI 現在幾點、這個對話上一則訊息是多久以前;每個對話第一次發言時,也告訴它自己平常一回合實際花多久。這些情境是我設計它要解決的(例如「我去睡了」隔天回來、resume 幾週前的對話、把 10 分鐘的事估成 1~2 天),AI 看到後怎麼反應由它自己決定,不是保證的效果。每次只加幾十個 token,連續聊天時完全不加。安裝時選 user 範圍=所有專案都有效。

Source 1 files
hooks/register.ts 84 lines
1import type { Register } from 'claude-code'
2
3const GAP_MS = 20 * 60_000 // only speak up after 20 min of silence
4const KEEP = 40 // turn durations remembered
5const MIN_SAMPLES = 5
6
7let sessionNoted = false
8let turnStartedAt: number | undefined
9
10function pad(n: number) {
11  return String(n).padStart(2, '0')
12}
13
14function stamp(ms: number) {
15  const d = new Date(ms)
16  const day = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getDay()]
17  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${day} ${pad(d.getHours())}:${pad(d.getMinutes())}`
18}
19
20function span(ms: number) {
21  const m = Math.round(ms / 60_000)
22  if (m < 60) return `${m}m`
23  const h = Math.floor(m / 60)
24  return h < 48 ? `${h}h${pad(m % 60)}m` : `${Math.floor(h / 24)}d${h % 24}h`
25}
26
27async function paceLine($: any) {
28  const list = ((await $.store.get('durations')) as number[] | undefined) ?? []
29  if (list.length < MIN_SAMPLES) return undefined
30  const s = [...list].sort((a, b) => a - b)
31  const median = s[Math.floor(s.length / 2)]
32  return `[pace] your last ${s.length} turns took median ${span(median)}, longest ${span(s[s.length - 1])} of real time. Estimate durations in these terms, not human workdays.`
33}
34
35// Per-conversation last-active time, so a resumed or overnight conversation sees its own gap.
36async function markActive($: any, id: string, now: number) {
37  const map = ((await $.store.get('lastBySession')) as Record<string, number> | undefined) ?? {}
38  map[id] = now
39  const keep = Object.entries(map).sort((a, b) => b[1] - a[1]).slice(0, 200)
40  await $.store.set('lastBySession', Object.fromEntries(keep))
41  await $.store.set('lastActive', now)
42}
43
44export const register: Register = on => {
45  on('prompt.submit', async ($, e, next) => {
46    const now = await $.clock.now()
47    const id = await $.session.id()
48    const bySession = ((await $.store.get('lastBySession')) as Record<string, number> | undefined) ?? {}
49    const mine = bySession[id]
50    const any = (await $.store.get('lastActive')) as number | undefined
51    await markActive($, id, now)
52    const last = mine ?? any
53    const notes: string[] = []
54    if (last === undefined || now - last >= GAP_MS || !sessionNoted) {
55      const ago = last === undefined ? '' : mine !== undefined
56        ? ` · this conversation's last message ${span(now - mine)} ago`
57        : ` · last activity ${span(now - last)} ago`
58      notes.push(`[time] now ${stamp(now)}` + ago)
59    }
60    if (!sessionNoted) {
61      sessionNoted = true
62      const pace = await paceLine($)
63      if (pace) notes.push(pace)
64    }
65    return notes.length ? next({ ...e, context: [...(e.context ?? []), notes.join(String.fromCharCode(10))] }) : next(e)
66  }).catch(($, e, next) => next(e))
67
68  on('turn.start', async ($, e, next) => {
69    if (!(e as any).agentId) turnStartedAt = await $.clock.now()
70    return next(e)
71  })
72
73  on('turn.complete', async ($, e, next) => {
74    if (!(e as any).agentId && turnStartedAt !== undefined) {
75      const now = await $.clock.now()
76      const list = ((await $.store.get('durations')) as number[] | undefined) ?? []
77      await $.store.set('durations', [...list, now - turnStartedAt].slice(-KEEP))
78      await markActive($, await $.session.id(), now)
79      turnStartedAt = undefined
80    }
81    return next(e)
82  })
83}
84