a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
967 lines
32 KiB
Markdown
967 lines
32 KiB
Markdown
# Ambient Mode
|
|
|
|
> **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.
|
|
|
|
## Overview
|
|
|
|
Ambient mode operates as a background loop that:
|
|
1. **Gardens** — consolidates, prunes, and strengthens the memory graph
|
|
2. **Scouts** — analyzes recent sessions, git history, and memories to understand what the user cares about
|
|
3. **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:**
|
|
1. **Single agent at a time** — only one ambient instance ever runs, no parallelism
|
|
2. **Subscription-first** — defaults to OAuth (OpenAI/Anthropic), never uses API keys unless explicitly configured
|
|
3. **User priority** — interactive sessions always take precedence over ambient work
|
|
4. **Strong models** — uses the strongest available model from the selected provider so the agent can reason well about what's actually useful
|
|
5. **Self-scheduling** — the agent decides when to wake next, constrained by adaptive resource limits
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```mermaid
|
|
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
|
|
```
|
|
|
|
---
|
|
|
|
## Ambient Cycle
|
|
|
|
Each ambient cycle follows a single flow. The agent doesn't switch between "modes" — it naturally handles gardening, scouting, and work in one pass.
|
|
|
|
```mermaid
|
|
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)
|
|
```
|
|
|
|
---
|
|
|
|
## Ambient Agent Tools
|
|
|
|
The ambient agent has access to a subset of jcode tools plus ambient-specific tools.
|
|
|
|
### `end_ambient_cycle` (required)
|
|
|
|
Every ambient cycle **must** end with this tool call. The system uses the summary for the notification email and the info widget.
|
|
|
|
```rust
|
|
// 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) |
|
|
|
|
### `schedule_ambient`
|
|
|
|
Can also be called mid-cycle to queue future work:
|
|
|
|
```rust
|
|
// Tool: schedule_ambient
|
|
{
|
|
"wake_in_minutes": 15,
|
|
"context": "Check if CI passed for auth refactor PR",
|
|
"priority": "normal"
|
|
}
|
|
```
|
|
|
|
### `todos`
|
|
|
|
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
|
|
|
|
### `request_permission`
|
|
|
|
From the [Safety System](./SAFETY_SYSTEM.md). Used for any Tier 2 action.
|
|
|
|
---
|
|
|
|
## Handling Unexpected Stops
|
|
|
|
The model may stop unexpectedly (output length limit, API error, random stop). The system handles this:
|
|
|
|
```mermaid
|
|
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_minutes` from config
|
|
- Warning logged — the agent should always schedule its next wake
|
|
|
|
---
|
|
|
|
## System Prompt
|
|
|
|
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.
|
|
```
|
|
|
|
---
|
|
|
|
## Usage Calculation
|
|
|
|
### Tracking
|
|
|
|
Every API call (user or ambient) is logged:
|
|
|
|
```rust
|
|
struct UsageRecord {
|
|
timestamp: DateTime<Utc>,
|
|
source: UsageSource, // User | Ambient
|
|
tokens_input: u32,
|
|
tokens_output: u32,
|
|
provider: String,
|
|
}
|
|
```
|
|
|
|
### Rate Limit Discovery
|
|
|
|
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.
|
|
|
|
### Adaptive Interval Algorithm
|
|
|
|
```
|
|
# 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)
|
|
```
|
|
|
|
### Behavioral Rules
|
|
|
|
| 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
|
|
|
|
### Two-Layer Architecture
|
|
|
|
Memory consolidation happens at two levels, mirroring how the brain encodes during the day and consolidates during sleep.
|
|
|
|
```mermaid
|
|
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
|
|
```
|
|
|
|
### Layer 1: Sidecar Consolidation
|
|
|
|
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.
|
|
|
|
### Layer 2: Ambient Garden
|
|
|
|
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 |
|
|
|
|
### Reinforcement Provenance
|
|
|
|
When a memory is reinforced (by sidecar or ambient), the system records a breadcrumb for traceability:
|
|
|
|
```rust
|
|
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.
|
|
|
|
---
|
|
|
|
## Scheduling
|
|
|
|
### Two-Layer Scheduling
|
|
|
|
```mermaid
|
|
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
|
|
```
|
|
|
|
### Agent-Initiated Scheduling
|
|
|
|
The ambient agent has a `schedule_ambient` tool to request its next wake-up:
|
|
|
|
```rust
|
|
// 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.
|
|
|
|
### Adaptive Resource Calculation
|
|
|
|
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
|
|
|
|
### Event Triggers
|
|
|
|
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 |
|
|
|
|
### Scheduled Queue
|
|
|
|
Persistent queue of scheduled ambient tasks:
|
|
|
|
```rust
|
|
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
|
|
|
|
---
|
|
|
|
## Provider & Model Selection
|
|
|
|
### Default Priority
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
### Model Selection
|
|
|
|
| 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 |
|
|
|
|
### Resource Rules
|
|
|
|
1. **Subscription (OAuth — OpenAI/Anthropic):** Ambient is allowed, subject to adaptive rate limiting
|
|
2. **Pay-per-token (API keys, OpenRouter):** Off by default. Enable in config with optional daily budget cap
|
|
3. **User active:** Ambient pauses or throttles to minimum when user has an active session
|
|
4. **Rate limited:** If ambient hits a rate limit, back off aggressively (exponential backoff)
|
|
5. **Separate pools:** Prefer OpenAI for ambient when Anthropic is used interactively (and vice versa)
|
|
|
|
---
|
|
|
|
## Proactive Work
|
|
|
|
### What Ambient Does
|
|
|
|
The agent uses memories, recent sessions, and git history to identify useful work:
|
|
|
|
```mermaid
|
|
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
|
|
```
|
|
|
|
### Safety
|
|
|
|
Ambient mode operates under the [Safety System](./SAFETY_SYSTEM.md) — 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
|
|
|
|
---
|
|
|
|
## Info Widget
|
|
|
|
The TUI displays ambient mode status alongside existing widgets (memory, tokens, etc.).
|
|
|
|
### Widget Content
|
|
|
|
```
|
|
╭─ 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 |
|
|
|
|
### Budget Breakdown
|
|
|
|
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.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
```toml
|
|
[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/"
|
|
```
|
|
|
|
---
|
|
|
|
## Storage
|
|
|
|
```
|
|
~/.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
|
|
```
|
|
|
|
---
|
|
|
|
## Context Window Management
|
|
|
|
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.
|
|
|
|
---
|
|
|
|
## User Feedback via Memory
|
|
|
|
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.
|
|
|
|
---
|
|
|
|
## Crash Safety & Recovery
|
|
|
|
Ambient must assume the process can die at any point (battery death, crash, OOM, etc.) and design so nothing is lost or corrupted.
|
|
|
|
### Principles
|
|
|
|
- **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 `interrupted` rather than `completed`, so the user knows it didn't finish.
|
|
|
|
### Recovery on Restart
|
|
|
|
When ambient starts after an unexpected shutdown:
|
|
|
|
1. **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.
|
|
2. **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.
|
|
3. **Expired scheduled items** — still execute them. The context the agent stored is still valid, the work is just late.
|
|
4. **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.
|
|
|
|
### State Diagram
|
|
|
|
```mermaid
|
|
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
|
|
```
|
|
|
|
---
|
|
|
|
## Cold Start
|
|
|
|
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
|
|
|
|
---
|
|
|
|
## Per-Project Configuration
|
|
|
|
Some projects may need different ambient behavior (e.g. sensitive work projects, personal repos with different preferences):
|
|
|
|
```toml
|
|
# 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 = false
|
|
```
|
|
|
|
---
|
|
|
|
## Multi-Machine (Deferred)
|
|
|
|
When 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
|
|
|
|
---
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Foundation
|
|
- [ ] 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
|
|
|
|
### Phase 2: Memory Consolidation — Garden
|
|
- [ ] 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
|
|
|
|
### Phase 3: Scheduling
|
|
- [ ] `schedule_ambient` tool 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
|
|
|
|
### Phase 4: Proactive Work
|
|
- [ ] Scout: analyze recent sessions + git history
|
|
- [ ] Infer user priorities from memories
|
|
- [ ] Identify actionable work
|
|
- [ ] Execute on separate branch
|
|
- [ ] Report results
|
|
|
|
### Phase 5: Info Widget
|
|
- [ ] 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*
|