Status: Design Updated: 2026-02-08
A proactive, always-on agent mode that works autonomously without user prompting. Like a brain consolidating memories during sleep, ambient mode tends to the memory graph, identifies useful work, and acts on the user's behalf — all while staying within resource limits.
Ambient mode operates as a background loop that:
- Gardens — consolidates, prunes, and strengthens the memory graph
- Scouts — analyzes recent sessions, git history, and memories to understand what the user cares about
- Works — proactively completes tasks the user would appreciate being surprised by
These aren't separate phases. The agent does all three in a single pass — while looking at memories it naturally discovers maintenance work and identifies proactive opportunities simultaneously.
Key Design Decisions:
- Single agent at a time — only one ambient instance ever runs, no parallelism
- Subscription-first — defaults to OAuth (OpenAI/Anthropic), never uses API keys unless explicitly configured
- User priority — interactive sessions always take precedence over ambient work
- Strong models — uses the strongest available model from the selected provider so the agent can reason well about what's actually useful
- Self-scheduling — the agent decides when to wake next, constrained by adaptive resource limits
graph TB
subgraph "Scheduling Layer"
EV[Event Triggers<br/>session close, crash, git push]
TM[Timer<br/>agent-scheduled wake]
RC[Resource Calculator<br/>adaptive interval]
SQ[(Scheduled Queue<br/>persistent)]
end
subgraph "Ambient Agent"
QC[Check Queue]
SC[Scout<br/>memories + sessions + git]
GD[Garden<br/>consolidate + prune + verify]
WK[Work<br/>proactive tasks]
SA[schedule_ambient tool<br/>set next wake + context]
end
subgraph "Resource Awareness"
UH[Usage History<br/>rolling window]
RL[Rate Limits<br/>per provider]
AU[Ambient Usage<br/>current window]
AC[Active Sessions<br/>user activity]
end
subgraph "Outputs"
MG[(Memory Graph<br/>consolidated)]
CM[Commits & Changes]
IW[Info Widget<br/>TUI display]
end
EV -->|wake early| RC
TM -->|scheduled wake| RC
RC -->|"gate: safe to run?"| QC
SQ -->|pending items| QC
QC --> SC
SC --> GD
SC --> WK
GD --> MG
WK --> CM
SA -->|next wake + context| SQ
SA -->|proposed interval| RC
UH --> RC
RL --> RC
AU --> RC
AC --> RC
QC --> IW
SC --> IW
GD --> IW
WK --> IW
style EV fill:#fff3e0
style TM fill:#fff3e0
style RC fill:#ffcdd2
style SQ fill:#e3f2fd
style QC fill:#e8f5e9
style SC fill:#e8f5e9
style GD fill:#e8f5e9
style WK fill:#e8f5e9
Each ambient cycle follows a single flow. The agent doesn't switch between "modes" — it naturally handles gardening, scouting, and work in one pass.
sequenceDiagram
participant SYS as System Scheduler
participant RES as Resource Calculator
participant AMB as Ambient Agent
participant MEM as Memory Graph
participant CB as Codebase
participant Q as Scheduled Queue
SYS->>RES: Timer/event fired
RES->>RES: Check usage headroom
alt Over budget
RES->>SYS: Delay (recalculate interval)
else Safe to run
RES->>AMB: Spawn ambient agent
end
AMB->>Q: Check scheduled queue
alt Has queued items
Q-->>AMB: Return items + context
AMB->>MEM: Scout relevant memories for queued work
MEM-->>AMB: Context memories
AMB->>CB: Execute queued work
end
AMB->>MEM: Load memory graph
MEM-->>AMB: Full graph state
Note over AMB: Garden pass
AMB->>AMB: Find duplicates → merge & reinforce
AMB->>AMB: Find contradictions → resolve
AMB->>AMB: Find decayed memories → prune or re-verify
AMB->>CB: Verify stale facts against codebase
CB-->>AMB: Verification results
AMB->>MEM: Apply consolidation changes
Note over AMB: Scout pass (simultaneous)
AMB->>AMB: Analyze recent sessions for missed extractions
AMB->>AMB: Check git history for active work
AMB->>AMB: Identify proactive work opportunities
Note over AMB: Work pass
AMB->>CB: Execute proactive tasks
AMB->>MEM: Store new memories from findings
AMB->>AMB: end_ambient_cycle(summary, schedule)
AMB->>SYS: Done (summary → widget + email)
The ambient agent has access to a subset of jcode tools plus ambient-specific tools.
Every ambient cycle must end with this tool call. The system uses the summary for the notification email and the info widget.
// Tool: end_ambient_cycle
{
"summary": "Merged 3 duplicate memories, pruned 2 stale facts,
extracted memories from crashed session jcode-red-fox-1234",
"memories_modified": 8,
"compactions": 2,
"proactive_work": null,
"next_schedule": {
"wake_in_minutes": 25,
"context": "Verify 4 remaining stale facts"
}
}| Field | Required | Description |
|---|---|---|
summary |
yes | Human-readable summary of what was done (goes into email/widget) |
memories_modified |
yes | Count of memories created/merged/pruned/updated |
compactions |
yes | Number of context compactions during this cycle |
proactive_work |
no | Description of proactive code changes, if any |
next_schedule |
no | When to wake next + context (falls back to system default if omitted) |
Can also be called mid-cycle to queue future work:
// Tool: schedule_ambient
{
"wake_in_minutes": 15,
"context": "Check if CI passed for auth refactor PR",
"priority": "normal"
}The agent should use a todos tool to plan its cycle. This provides:
- Visibility into what the agent planned vs what it actually did
- If the cycle is interrupted, we know what's left
- Structure for the agent's reasoning
From the Safety System. Used for any Tier 2 action.
The model may stop unexpectedly (output length limit, API error, random stop). The system handles this:
stateDiagram-v2
[*] --> Running: Cycle started
Running --> Stopped: Model output ends
Stopped --> CheckTool{Called end_ambient_cycle?}
CheckTool --> Complete: Yes → normal completion
CheckTool --> Continuation: No → send continuation message
Continuation --> Running: Model continues work
Continuation --> Stopped: Model stops again
Stopped --> ForcedEnd: Second stop without end_ambient_cycle
ForcedEnd --> Incomplete: Generate partial transcript,\nschedule default wake
Complete --> [*]
Incomplete --> [*]
Continuation message (injected as user message):
You stopped unexpectedly without calling end_ambient_cycle.
If you are done with your work, call end_ambient_cycle with a
summary of what you accomplished and schedule your next wake.
If you are not done, continue what you were doing.
If no end_ambient_cycle is called after two attempts:
- System generates a partial transcript marked as
incomplete - Compaction count is pulled from system metrics
- Default wake interval is scheduled
- Warning logged for debugging
If no schedule_ambient or next_schedule in end_ambient_cycle:
- System schedules a default wake at
max_interval_minutesfrom config - Warning logged — the agent should always schedule its next wake
The ambient agent's system prompt is built dynamically each cycle with real data. The prompt gives the agent information to reason with, not rigid instructions for how to think.
You are the ambient agent for jcode. You operate autonomously without
user prompting. Your job is to maintain and improve the user's
development environment.
## Current State
- Last ambient cycle: {timestamp} ({time_ago})
- Machine was off/idle since: {if applicable}
- Active user sessions: {count, or "none"}
- Cycle budget: ~{estimated_max_tokens} tokens
## Scheduled Queue
{queued items with context, or "empty — do general ambient work"}
## Recent Sessions (since last cycle)
{for each session:
- id, status (closed/crashed/active), duration, topic summary
- extraction status (extracted/missed/partial)
}
## Memory Graph Health
- Total memories: {count} ({active} active, {inactive} inactive)
- Memories with confidence < 0.1: {count}
- Unresolved contradictions: {count}
- Memories without embeddings: {count}
- Duplicate candidates (similarity > 0.95): {count}
- Last consolidation: {timestamp}
## User Feedback History
{recent memories about ambient approval/rejection patterns}
## Resource Budget
- Provider: {name}
- Tokens remaining in window: {count}
- Window resets: {timestamp}
- User usage rate: {tokens/min average}
- Budget for this cycle: stay under {limit} tokens
## Instructions
Start by using the todos tool to plan what you'll do this cycle.
Priority order:
1. Execute any scheduled queue items first.
2. Garden the memory graph — consolidate duplicates, resolve
contradictions, prune dead memories, verify stale facts,
extract from missed sessions.
3. Scout for proactive work (only if enabled and past cold start) —
look at recent sessions and git history to identify useful work
the user would appreciate.
For gardening: focus on highest-value maintenance first. Duplicates
and contradictions before pruning. Verify stale facts only if you
have budget left.
For proactive work: be conservative. A bad surprise is worse than
no surprise. Check the user feedback memories — if they've rejected
similar work before, don't do it. Code changes must go on a worktree
branch with a PR via request_permission.
When done, you MUST call end_ambient_cycle with a summary of
everything you did, including compaction count. Always schedule
your next wake time with context for what you plan to do next.
Every API call (user or ambient) is logged:
struct UsageRecord {
timestamp: DateTime<Utc>,
source: UsageSource, // User | Ambient
tokens_input: u32,
tokens_output: u32,
provider: String,
}Rate limits are learned from provider response headers:
x-ratelimit-limit-requests: 50
x-ratelimit-remaining-requests: 42
x-ratelimit-limit-tokens: 100000
x-ratelimit-remaining-tokens: 85000
x-ratelimit-reset-requests: 2026-02-08T15:00:00Z
When headers aren't available, fall back to conservative defaults and adjust based on whether rate limit errors occur.
# Known from headers or defaults
window_remaining = reset_time - now
tokens_remaining = ratelimit_remaining_tokens
requests_remaining = ratelimit_remaining_requests
# Estimate user consumption from rolling history
user_rate = rolling_average(
usage_log.filter(source=User, last_hour),
per_minute
)
# Project user usage for rest of window
user_projected = user_rate * window_remaining
# Reserve 20% buffer so user never feels throttled
ambient_budget = (tokens_remaining - user_projected) * 0.8
# Estimate cost per ambient cycle from recent cycles
tokens_per_cycle = rolling_average(
recent_ambient_cycles.last(5).tokens_used
)
# How many cycles fit in remaining budget?
cycles_available = ambient_budget / tokens_per_cycle
# Spread evenly across remaining window
if cycles_available > 0:
interval = window_remaining / cycles_available
else:
interval = window_remaining # wait for reset
# Clamp to configured bounds
interval = clamp(interval, min_interval, max_interval)
| Condition | Behavior |
|---|---|
| User is active in a session | Pause ambient (or multiply interval by 3-5x) |
| User has been idle for hours | Run cycles more frequently |
| Hit a rate limit | Exponential backoff (double interval each time) |
| No rate limit errors for N cycles | Gradually decrease interval |
| No headers available | Start with 30min interval, adjust from errors |
| Approaching end of window with budget left | Squeeze in extra cycles |
| Over 80% of budget consumed | Fall back to max_interval |
Memory consolidation happens at two levels, mirroring how the brain encodes during the day and consolidates during sleep.
graph LR
subgraph "Layer 1: Sidecar (every turn, fast)"
S1[Memory retrieved<br/>for relevance check]
S2{New memory<br/>similar to existing?}
S3[Reinforce existing<br/>+ breadcrumb]
S4[Create new memory]
S5[Supersede if<br/>contradicts]
end
subgraph "Layer 2: Ambient Garden (background, deep)"
A1[Full graph scan]
A2[Cross-session<br/>dedup]
A3[Fact verification<br/>against codebase]
A4[Retroactive<br/>session extraction]
A5[Prune dead<br/>memories]
A6[Relationship<br/>discovery]
end
S1 --> S2
S2 -->|yes| S3
S2 -->|no| S4
S2 -->|contradicts| S5
A1 --> A2
A1 --> A3
A1 --> A4
A1 --> A5
A1 --> A6
style S1 fill:#e8f5e9
style S2 fill:#e8f5e9
style S3 fill:#e8f5e9
style S4 fill:#e8f5e9
style S5 fill:#e8f5e9
style A1 fill:#e3f2fd
style A2 fill:#e3f2fd
style A3 fill:#e3f2fd
style A4 fill:#e3f2fd
style A5 fill:#e3f2fd
style A6 fill:#e3f2fd
Runs after every turn, only on memories already retrieved for relevance checking. Zero added latency — runs after results are returned to the main agent.
Operations:
- Duplicate detection — if the sidecar is about to create a memory that's semantically identical to one it just retrieved, reinforce the existing one instead
- Contradiction detection — if a new memory contradicts an existing one in the retrieved set, supersede the old one
- Reinforcement — bump strength on memories that keep appearing relevant
Cost: Near zero. Only operates on memories already in hand.
Deep consolidation that runs during ambient cycles. Has access to the full memory graph and codebase.
Operations:
| Operation | Description | Trigger |
|---|---|---|
| Graph-wide dedup | Find semantically similar memories across entire graph | Embedding similarity > 0.95 |
| Contradiction resolution | Resolve Contradicts edges by checking current state |
Contradicts edges exist |
| Fact verification | Check factual memories against codebase | Facts older than confidence half-life |
| Retroactive extraction | Analyze recent sessions that lack memory extraction | Sessions with status Crashed, Closed without extraction |
| Pruning | Remove memories with near-zero confidence and low strength | confidence < 0.05 AND strength <= 1 |
| Relationship discovery | Find new connections between memories | Co-occurrence in sessions, semantic similarity |
| Embedding backfill | Generate embeddings for memories that lack them | embedding is None |
| Cluster refinement | Re-run clustering on updated embeddings | Every N ambient cycles |
When a memory is reinforced (by sidecar or ambient), the system records a breadcrumb for traceability:
pub struct Reinforcement {
pub session_id: String,
pub message_index: usize,
pub timestamp: DateTime<Utc>,
}
pub struct MemoryEntry {
// ... existing fields ...
pub reinforcements: Vec<Reinforcement>,
}
impl MemoryEntry {
pub fn reinforce(&mut self, session_id: &str, message_index: usize) {
self.strength += 1;
self.updated_at = Utc::now();
self.reinforcements.push(Reinforcement {
session_id: session_id.to_string(),
message_index,
timestamp: Utc::now(),
});
}
}The consolidation agent can later trace back through reinforcements to understand why a memory has the strength it does, and whether those reinforcements still hold.
graph TB
subgraph "Agent Layer (proposes)"
AT[schedule_ambient tool]
AT -->|"wake in 15m,<br/>context: check CI"| PROP[Proposed Schedule]
end
subgraph "System Layer (constrains)"
PROP --> ADAPT[Adaptive Calculator]
MAX[Max Interval Ceiling] --> ADAPT
MIN[Min Interval Floor] --> ADAPT
ADAPT --> FINAL[Final Schedule]
end
subgraph "Adaptive Calculator Inputs"
UH[User usage history<br/>rolling window]
AU[Ambient usage<br/>current window]
RL[Provider rate limits<br/>from headers]
TW[Time remaining<br/>in limit window]
AS[Active sessions<br/>user currently working?]
end
UH --> ADAPT
AU --> ADAPT
RL --> ADAPT
TW --> ADAPT
AS --> ADAPT
FINAL -->|"actual: 28m<br/>(headroom limited)"| TIMER[System Timer]
style AT fill:#e8f5e9
style ADAPT fill:#ffcdd2
style FINAL fill:#e3f2fd
The ambient agent has a schedule_ambient tool to request its next wake-up:
// Tool: schedule_ambient
{
"wake_in_minutes": 15, // or "wake_at": "2026-02-08T15:30:00Z"
"context": "Check if CI passed for auth refactor PR",
"priority": "normal" // "low" | "normal" | "high"
}The context is stored in the scheduled queue so when the agent wakes up, it knows what it planned to do.
The system calculates the safe interval based on usage patterns:
headroom = rate_limit - (user_usage_rate + ambient_usage_rate)
safe_interval = max(min_interval, target_budget_fraction / headroom)
Inputs:
- User usage rate — rolling average of tokens/requests per hour from interactive sessions
- Ambient usage rate — tokens/requests consumed by ambient in current window
- Rate limits — known per-provider limits (from response headers or config)
- Time in window — how much of the rate limit window remains
- Active sessions — if user is currently in a session, ambient pauses or throttles heavily
Behavior:
- Agent says "wake in 10m" but system calculates "not safe until 30m" → pushed to 30m
- Agent says "wake in 6h" but system sees unused budget → pulled forward to max interval
- User starts interactive session → ambient pauses, resumes when user goes idle
- Approaching rate limit → ambient backs off exponentially
Certain events can wake ambient early (still subject to resource gate):
| Event | Priority | Rationale |
|---|---|---|
| Session crashed | High | Likely missed memory extraction |
| Session closed | Normal | May have unextracted memories |
| Git push | Low | Codebase changed, facts may be stale |
| User idle > threshold | Low | Good time for ambient work |
Explicit /ambient command |
Immediate | User requested |
Persistent queue of scheduled ambient tasks:
pub struct ScheduledItem {
pub id: String,
pub scheduled_for: DateTime<Utc>,
pub context: String,
pub priority: Priority,
pub created_by_session: String, // which ambient cycle created this
pub created_at: DateTime<Utc>,
}
pub enum Priority {
Low,
Normal,
High,
}Queue rules:
- Checked first when ambient wakes up
- Items sorted by priority then scheduled time
- Expired items (past their scheduled_for) are still executed
- System can delay items if over budget, but won't drop them
- Only one ambient agent at a time — if one is running, new triggers queue up
graph TD
START[Ambient Mode Start] --> CHECK1{OpenAI OAuth<br/>available?}
CHECK1 -->|yes| OAI[Use OpenAI<br/>strongest available]
CHECK1 -->|no| CHECK2{Anthropic OAuth<br/>available?}
CHECK2 -->|yes| ANT[Use Anthropic<br/>strongest available]
CHECK2 -->|no| CHECK3{API key or OpenRouter +<br/>config opt-in?}
CHECK3 -->|yes| API[Use API/OpenRouter<br/>with budget cap]
CHECK3 -->|no| DISABLED[Ambient mode disabled<br/>no provider available]
style OAI fill:#e8f5e9
style ANT fill:#fff3e0
style API fill:#ffcdd2
style DISABLED fill:#f5f5f5
Rationale:
- OpenAI first — separate rate limit pool from Anthropic, so ambient doesn't compete with interactive sessions
- Anthropic second — also subscription-based (OAuth), no per-token cost
- OpenRouter/API keys last — these are pay-per-token; opt-in only via config to avoid silently burning credits
- Strong models — ambient needs good judgment about what work is valuable. A weak model would do the wrong proactive work and annoy the user.
| Provider | Default Model | Rationale |
|---|---|---|
| OpenAI OAuth | Strongest available (e.g. 5.2-codex-xhigh) |
Best reasoning for judgment calls |
| Anthropic OAuth | Strongest available (e.g. claude-opus-4-6) |
Best available on Anthropic |
| OpenRouter (opt-in) | Strongest available | Pay-per-token, requires config opt-in |
| API key (opt-in) | Configurable | User chooses cost/capability tradeoff |
- Subscription (OAuth — OpenAI/Anthropic): Ambient is allowed, subject to adaptive rate limiting
- Pay-per-token (API keys, OpenRouter): Off by default. Enable in config with optional daily budget cap
- User active: Ambient pauses or throttles to minimum when user has an active session
- Rate limited: If ambient hits a rate limit, back off aggressively (exponential backoff)
- Separate pools: Prefer OpenAI for ambient when Anthropic is used interactively (and vice versa)
The agent uses memories, recent sessions, and git history to identify useful work:
graph LR
subgraph "Context Gathering"
M[Memories<br/>user preferences,<br/>priorities]
S[Recent Sessions<br/>what user was<br/>working on]
G[Git History<br/>active branches,<br/>recent changes]
end
subgraph "Inference"
I[What does the user<br/>care about most?]
U[What upcoming work<br/>is there?]
O[What would surprise<br/>the user positively?]
end
subgraph "Actions"
T[Write/fix tests]
R[Small refactors]
D[Update stale docs]
F[Fix obvious issues]
C[Clean up TODOs]
end
M --> I
S --> I
G --> I
I --> O
U --> O
O --> T
O --> R
O --> D
O --> F
O --> C
Ambient mode operates under the Safety System — a human-in-the-loop layer that classifies actions, requests permission for anything risky, and notifies the user via email/SMS/desktop.
Key constraints for ambient:
- All actions classified — auto-allowed (read, local branches, memory ops), requires permission (PRs, pushes, communication), or always denied (force-push, delete remote branches)
- Commits to a separate branch — never pushes to main/master directly
- Code changes require worktree + PR — modifications always go through review
- Small, focused changes — no large refactors without user request
- Session transcript — full log of every action, sent as summary after each cycle
- Respects .gitignore and sensitive files — same security rules as interactive mode
- Can be reviewed — user sees ambient work in the TUI and pending permission requests
The TUI displays ambient mode status alongside existing widgets (memory, tokens, etc.).
╭─ Ambient ─────────────────────────╮
│ ● Running (garden + scout) │
│ Queue: 2 items (next: check CI) │
│ Last: 12m ago — pruned 3, merged 1│
│ Next: ~18m (adaptive) │
│ Budget: ██████░░░░ 58% remaining │
╰───────────────────────────────────╯
Fields:
| Field | Description |
|---|---|
| Status | idle / running (detail) / scheduled / paused (rate limited) |
| Queue | Count of scheduled items + preview of next one's context |
| Last cycle | Time since last run + summary of what it did |
| Next wake | Estimated time until next cycle (from adaptive calculator) |
| Budget | Visual bar showing usage: user + ambient + remaining headroom |
The budget bar shows three segments:
User usage Ambient usage Remaining
████████████ ████ ░░░░░░░░░░
45% 12% 43%
This gives the user immediate visibility into whether ambient is being too aggressive.
[ambient]
# Enable ambient mode (default: false until stable)
enabled = false
# Provider override (default: auto-select per priority chain)
# provider = "openai"
# Model override (default: provider's strongest)
# model = "5.2-codex-xhigh"
# Allow API key usage (default: false, only OAuth)
allow_api_keys = false
# Daily token budget when using API keys (ignored for OAuth)
# api_daily_budget = 100000
# Minimum interval between cycles in minutes (default: 5)
min_interval_minutes = 5
# Maximum interval between cycles in minutes (default: 120)
max_interval_minutes = 120
# Pause ambient when user has active session (default: true)
pause_on_active_session = true
# Enable proactive work (vs garden-only mode) (default: true)
proactive_work = true
# Proactive work branch prefix (default: "ambient/")
work_branch_prefix = "ambient/"~/.jcode/ambient/
├── state.json # Current ambient state (status, last run, etc.)
├── queue.json # Scheduled queue (persistent across restarts)
├── usage.json # Usage history for adaptive calculation
└── logs/
└── ambient-YYYY-MM-DD.log # Daily ambient activity logs
Ambient mode uses the same compaction strategy as interactive sessions: compact at 80% context window usage. No special handling needed — if an ambient cycle is analyzing a large memory graph or many sessions, it compacts and continues.
Ambient learns from the user's approval/rejection decisions through the memory system itself. No separate feedback mechanism is needed.
- User rejects a proactive change → ambient stores a memory: "User rejected ambient PR to refactor auth tests — prefers not to have tests auto-modified"
- User approves → memory: "User approved ambient fixing typos in docs"
- Pattern emerges → these memories get reinforced over time, naturally influencing what ambient prioritizes
This works because ambient already scouts memories before deciding what to do. Its own approval/rejection history becomes part of the context it reasons about, and these memories consolidate, decay, and reinforce like everything else in the graph.
Ambient must assume the process can die at any point (battery death, crash, OOM, etc.) and design so nothing is lost or corrupted.
- Atomic writes — memory graph and state files are written to a temp file first, then atomically renamed. A crash mid-write doesn't corrupt existing data.
- Incremental checkpointing — if ambient is halfway through gardening 50 memories and crashes, it shouldn't redo the ones already finished. A "last processed" marker tracks progress within a cycle.
- Persistent queue survives crashes — scheduled queue and permission requests are on disk, not in memory. They survive restarts.
- Interrupted transcripts — if a cycle doesn't complete, the transcript is marked as
interruptedrather thancompleted, so the user knows it didn't finish.
When ambient starts after an unexpected shutdown:
- Don't replay missed cycles — don't try to run every cycle that was scheduled while the machine was off. Just run one cycle that examines current state.
- Check time since last run — if the gap is large (hours/days), there may be a backlog of crashed sessions to extract, stale memories to verify, etc. The agent handles this naturally since it always checks current state rather than diffing from last run.
- Expired scheduled items — still execute them. The context the agent stored is still valid, the work is just late.
- Resume, don't restart — if a cycle was interrupted mid-way, check the checkpoint and continue from where it left off rather than starting over.
stateDiagram-v2
[*] --> Starting: jcode starts
Starting --> CheckLastRun: ambient enabled?
CheckLastRun --> NormalCycle: last run recent
CheckLastRun --> CatchUpCycle: last run stale (hours/days)
CheckLastRun --> ResumeCycle: interrupted cycle found
NormalCycle --> Sleeping: cycle complete
CatchUpCycle --> Sleeping: cycle complete
ResumeCycle --> Sleeping: cycle complete
Sleeping --> NormalCycle: timer/event fires
Sleeping --> [*]: machine off / crash
note right of CatchUpCycle: Single cycle examining\ncurrent state, not\nreplaying missed cycles
note right of ResumeCycle: Continue from\ncheckpoint marker
First time ambient runs, there's no usage history, no patterns, no feedback memories. Bootstrapping strategy:
- Start conservative — garden-only (memory maintenance), no proactive work until ambient has enough context
- Build usage baseline — first few cycles just observe and track usage patterns for the adaptive scheduler
- Proactive work unlocks gradually — after N successful garden cycles with user-approved results, ambient can start scouting for proactive work
- Or user opts in immediately — config option to skip the warm-up if the user trusts it
Some projects may need different ambient behavior (e.g. sensitive work projects, personal repos with different preferences):
# In project-level .jcode/config.toml
[ambient]
# Disable ambient entirely for this project
enabled = false
# Or restrict to garden-only (no proactive code changes)
proactive_work = falseWhen ambient runs on multiple machines (e.g. laptop + desktop), shared state could conflict: double-processing sessions, conflicting memory edits, overlapping proactive work.
This is a distributed systems problem that will be addressed once ambient is stable on a single machine. Potential approaches:
- Machine ID on memory writes for conflict resolution
- Lock file or leader election for exclusive operations
- Git worktrees are already isolated, so proactive work is naturally conflict-free
- Ambient agent loop (spawn, run, sleep)
- Single-instance guard
- Basic scheduling (fixed interval with max ceiling)
- Provider selection chain (OpenAI OAuth → Anthropic OAuth → pay-per-token opt-in → disabled)
- Configuration (
[ambient]section in config) - Storage layout
- Full graph-wide dedup scan
- Fact verification against codebase
- Retroactive session extraction (crashed/missed sessions)
- Pruning dead memories (low confidence + low strength)
- Relationship discovery across sessions
- Embedding backfill
- Contradiction resolution
-
schedule_ambienttool for agent self-scheduling - Scheduled queue (persistent, with context)
- Adaptive resource calculator
- Usage history tracking
- Rate limit awareness (from provider response headers)
- Event triggers (session close, crash, git push)
- Active session detection → pause/throttle
- Scout: analyze recent sessions + git history
- Infer user priorities from memories
- Identify actionable work
- Execute on separate branch
- Report results
- Ambient status display in TUI
- Queue preview
- Last cycle summary
- Next wake estimate
- Budget bar (user vs ambient vs remaining)
Last updated: 2026-02-08