Summary
jcode sessions on the same host are islands: none of them knows what a sibling
session is doing, has done, or plans to do. A user working in several terminals
(or running background/ambient tasks) has no built-in way to ask "what were we
doing in the other session?" — the answer requires manual copy-paste or a
separate process.
We propose native host-level shared state (active sessions, recent message
digest, todos, scheduled tasks, background tasks, memory summary) that every
session can read without breaking the prompt cache, so any session can pick
up where another left off.
Impact / use cases
- Multi-terminal workflows. User runs 2–3 jcode sessions in parallel; wants
"continue what the other terminal was doing" without re-explaining context.
- Background tasks. A task spawned from session A finishes while the user
is in session B; B should see status, progress and output tail.
- Handoff / crash recovery. A session is closed or dies; a new session
reconstructs context from the digest instead of a blank start.
- Autonomous work. Scheduled and ambient tasks leave state; any session (or
a fresh one) can report on them.
Current state: external shim (reference implementation)
There is no native support. On this host we built an external shim that
already delivers the behavior and is battle-tested in real use:
- A Python watcher (
jcode-sync-watcher.py, attached to this issue) polls
every 3 s while at least one session is alive and
aggregates jcode's own on-disk state into ~/.jcode/sync/ (a git repo):
conversations.jsonl — digest-only history: every message of every
session as ≤300-char summaries (tool calls → [tool: name], results →
[tool_result] + first 150 chars) with a ref field pointing at the full
text in ~/.jcode/sessions/. Append-only, dedup by message_id, trimmed
to the last 20000 records (~24 MB ceiling).
todos.json (mirror of ~/.jcode/todos/*.json), scheduled.json
(ambient/queue.json), memory.json + initiatives.json (compact
summaries), background.json (active sessions + event journal),
background-tasks.json (mirror of $TMPDIR/jcode-bg-tasks/*.status.json
overview.md — human-readable digest with a "last messages" tail.
- Started via the existing
[hooks] session_start hook (config.toml →
fire_session_lifecycle_hook("session_start", ...) at
crates/jcode-app-core/src/agent.rs:372,432 and
crates/jcode-app-core/src/agent/turn_execution.rs:643). The watcher
self-exits ~15 s after the last live session closes (pid-liveness check on
~/.jcode/active_pids, 60 s startup grace against the session-registration
race).
- New sessions are told about it through
~/.jcode/prompt-overlay.md, loaded by
load_prompt_overlay_files_from_dir (crates/jcode-base/src/prompt.rs:862),
which instructs the model to read ~/.jcode/sync/overview.md and the
conversations.jsonl tail before answering.
Why the shim is fragile — the gaps this issue wants closed:
- The overlay must stay static. Injecting dynamic state into the overlay
breaks the prompt cache (a 3 s regenerated overlay caused ~97K tokens of
system prompt re-sent per turn, cache_read → ~0). So live state cannot be
auto-injected; the model must remember to read the sync files, and it is
easy to skip.
- Duplicate of data jcode already holds. The shim re-aggregates sessions,
todos and background tasks that the daemon already has in memory. It is a
separate process with its own lifecycle (pid files, grace periods, lock)
competing with the daemon.
- No privacy controls.
conversations.jsonl contains message text. There
is no per-session opt-out, redaction, or retention policy.
- Cross-host sync is manual git push/pull.
Proposed design (native)
- Daemon-owned shared store. jcode maintains a compact host-level store
(e.g. ~/.jcode/state/shared.json), updated on events rather than by an
external poller: session start/end, message appended, todo change,
background-task status change, scheduled task due, memory write. Contents:
active sessions, last N messages per session (configurable), todos/plan/goals,
scheduled tasks, background tasks + output tails, memory/initiatives counts,
timestamp. Written atomically (tmp+rename); no external watcher process.
- Cache-safe access. Two complementary paths:
- Option A (tool, recommended): a
read_session_state-style tool
returning the digest on demand. Zero prompt-cache impact, explicit,
model-driven.
- Option B (pinned injection, opt-in): append a digest to the static
prompt part once at session start, never refreshed mid-session (protects
the KV cache, per the prompt-cache issue's "static by construction"
principle); refresh only on new session or explicit reload.
- Native lifecycle. Shared-state writer lives and dies with the daemon: no
watcher, no pid files, no grace periods.
- Privacy & retention. Per-session opt-out of transcript sharing;
configurable depth (digest vs full text) and retention limit; local-only mode
(no git).
- Cross-host sync (out of scope for v1). Later: reuse git or a future sync
server; for now, the store stays on the host.
Verification (on the shim)
- Cross-session Q&A: two concurrent sessions on one host; session B answered
"what was session A doing / what was the last thing done there" from shared
state alone, with no user copy-paste.
- Background tasks visible: a native background task spawned in one session
(status, progress %, output tail) shows up in overview.md and in the other
session's digest while still running.
- KV-cache preserved: the overlay is static (snapshot pinned in
prompt-overlay.md); fresh data is read from ~/.jcode/sync/ files on
demand.
- History stays small:
conversations.jsonl is digest-only (≤300 chars per
message, ref to full text); overview tail reads only the last 64 KB of the
file. A previously multi-MB transcript file is now ~KB-scale to re-read.
Acceptance criteria
- Two concurrent sessions on one host: session B can answer "what is session A
doing / what was the last thing done there" from shared state, with no user
copy-paste.
- Reading shared state never changes the cacheable static prefix mid-session
(KV cache preserved; the 97K-token incident does not recur).
- Background task status/progress/output tail started in any session is visible
in any other session and in the TUI.
- Privacy: a session with
share_transcripts=false never contributes message
text to the shared store.
- The external watcher shim becomes removable: the native implementation covers
the same surface.
jcode doctor / status widget reports shared-state health (age of last
update, stale flag).
Environment
- jcode v0.68.0 (fcf5390), Linux x86_64. Shim reference:
~/.jcode/sync/README.md
and the attached jcode-sync-watcher.py (+ helper scripts jcode-sync.sh,
jcode-sync-start.sh, jcode-sync-bg.sh).
Related
jcode-sync.sh
jcode-sync-bg.sh
jcode-sync-start.sh
jcode-sync-watcher.py
Summary
jcode sessions on the same host are islands: none of them knows what a sibling
session is doing, has done, or plans to do. A user working in several terminals
(or running background/ambient tasks) has no built-in way to ask "what were we
doing in the other session?" — the answer requires manual copy-paste or a
separate process.
We propose native host-level shared state (active sessions, recent message
digest, todos, scheduled tasks, background tasks, memory summary) that every
session can read without breaking the prompt cache, so any session can pick
up where another left off.
Impact / use cases
"continue what the other terminal was doing" without re-explaining context.
is in session B; B should see status, progress and output tail.
reconstructs context from the digest instead of a blank start.
a fresh one) can report on them.
Current state: external shim (reference implementation)
There is no native support. On this host we built an external shim that
already delivers the behavior and is battle-tested in real use:
jcode-sync-watcher.py, attached to this issue) pollsevery 3 s while at least one session is alive and
aggregates jcode's own on-disk state into
~/.jcode/sync/(a git repo):conversations.jsonl— digest-only history: every message of everysession as ≤300-char summaries (tool calls →
[tool: name], results →[tool_result]+ first 150 chars) with areffield pointing at the fulltext in
~/.jcode/sessions/. Append-only, dedup bymessage_id, trimmedto the last 20000 records (~24 MB ceiling).
todos.json(mirror of~/.jcode/todos/*.json),scheduled.json(
ambient/queue.json),memory.json+initiatives.json(compactsummaries),
background.json(active sessions + event journal),background-tasks.json(mirror of$TMPDIR/jcode-bg-tasks/*.status.jsonoverview.md— human-readable digest with a "last messages" tail.[hooks] session_starthook (config.toml→fire_session_lifecycle_hook("session_start", ...)atcrates/jcode-app-core/src/agent.rs:372,432andcrates/jcode-app-core/src/agent/turn_execution.rs:643). The watcherself-exits ~15 s after the last live session closes (pid-liveness check on
~/.jcode/active_pids, 60 s startup grace against the session-registrationrace).
~/.jcode/prompt-overlay.md, loaded byload_prompt_overlay_files_from_dir(crates/jcode-base/src/prompt.rs:862),which instructs the model to read
~/.jcode/sync/overview.mdand theconversations.jsonltail before answering.Why the shim is fragile — the gaps this issue wants closed:
breaks the prompt cache (a 3 s regenerated overlay caused ~97K tokens of
system prompt re-sent per turn,
cache_read→ ~0). So live state cannot beauto-injected; the model must remember to read the sync files, and it is
easy to skip.
todos and background tasks that the daemon already has in memory. It is a
separate process with its own lifecycle (pid files, grace periods, lock)
competing with the daemon.
conversations.jsonlcontains message text. Thereis no per-session opt-out, redaction, or retention policy.
Proposed design (native)
(e.g.
~/.jcode/state/shared.json), updated on events rather than by anexternal poller: session start/end, message appended, todo change,
background-task status change, scheduled task due, memory write. Contents:
active sessions, last N messages per session (configurable), todos/plan/goals,
scheduled tasks, background tasks + output tails, memory/initiatives counts,
timestamp. Written atomically (tmp+rename); no external watcher process.
read_session_state-style toolreturning the digest on demand. Zero prompt-cache impact, explicit,
model-driven.
prompt part once at session start, never refreshed mid-session (protects
the KV cache, per the prompt-cache issue's "static by construction"
principle); refresh only on new session or explicit reload.
watcher, no pid files, no grace periods.
configurable depth (digest vs full text) and retention limit; local-only mode
(no git).
server; for now, the store stays on the host.
Verification (on the shim)
"what was session A doing / what was the last thing done there" from shared
state alone, with no user copy-paste.
(status, progress %, output tail) shows up in
overview.mdand in the othersession's digest while still running.
prompt-overlay.md); fresh data is read from~/.jcode/sync/files ondemand.
conversations.jsonlis digest-only (≤300 chars permessage,
refto full text); overview tail reads only the last 64 KB of thefile. A previously multi-MB transcript file is now ~KB-scale to re-read.
Acceptance criteria
doing / what was the last thing done there" from shared state, with no user
copy-paste.
(KV cache preserved; the 97K-token incident does not recur).
in any other session and in the TUI.
share_transcripts=falsenever contributes messagetext to the shared store.
the same surface.
jcode doctor/ status widget reports shared-state health (age of lastupdate, stale flag).
Environment
~/.jcode/sync/README.mdand the attached
jcode-sync-watcher.py(+ helper scriptsjcode-sync.sh,jcode-sync-start.sh,jcode-sync-bg.sh).Related
jcode-sync.sh
jcode-sync-bg.sh
jcode-sync-start.sh
jcode-sync-watcher.py