Base Context combines daemon-backed session workers with persistent state, scheduled prompts, direct agent messaging, goals, and bounded autonomous continuations. These features serve different purposes but share the same session and worker runtime.
flowchart TD
client["TUI or CLI client"]
peer["Peer agent or retained subagent"]
supervisor["Daemon supervisor<br/>routing + attachments"]
subgraph worker["Resident session worker"]
heartbeat["User + RLM heartbeats"]
schedule["One-time + cron schedules"]
goal["Persistent goal"]
autonomous["Autonomous mode"]
policy["Continuation policy"]
queue["Session prompt queue"]
session["AgentSession"]
kernel["Worker-owned Python kernel"]
children["RLM child sessions"]
heartbeat --> queue
schedule --> queue
goal --> policy
autonomous --> policy
policy --> queue
queue --> session
session --> kernel
session <--> children
end
artifacts["Native-framed journal + session artifacts"]
client <-->|"attach · detach · commands"| supervisor
peer -->|"direct message"| supervisor
supervisor --> queue
session --> artifacts
artifacts -. "supported recovery" .-> session
The client can detach at any point. The resident worker continues to own the queue, schedules, session, kernel, descendants, and persisted state.
Normal interactive sessions run in resident worker processes managed by a local supervisor. The worker owns the root session, its Python kernel, scheduled jobs, and RLM descendants.
Closing the terminal UI detaches the client; it does not stop the resident worker. Print/JSON and RPC sessions are client-owned instead: losing their client connection triggers worker cleanup. ACP sessions are resident unless --no-session is used. A separate launcher or monitoring process can fail while its CLI child remains connected; that is not a client disconnect.
List and reconnect to active agents with:
base-context list
base-context attach <agent>Other lifecycle commands are:
base-context agents # Open the agents view
base-context rename <agent> <name> # Give an agent a stable readable name
base-context stop <agent> # Stop one agent
base-context status # Inspect background services
base-context doctor [--fix] # Diagnose or repair service state
base-context shutdown [--force] # Stop all agents and servicesUse the session ID from the JSON stream header or the IDs in base-context list --json to track a run. Several sessions can share one cwd. The process title base-context identifies the program, not an individual session. stop reports an error for an unknown or ambiguous target; do not suppress all stop failures or infer worker death from a separate launcher's exit.
Workers persist native-framed journals and use derived indexes. The default flat paths are ~/.base-context/sessions/<session-id>.jsonl and ~/.base-context/session-artifacts/<session-id>/. BASE_CONTEXT_HOME selects the product root; BASE_CONTEXT_SESSION_DIR can select a different absolute sessions directory, with artifacts under its parent's session-artifacts/ directory.
The .jsonl extension does not make a native journal an editable transcript. Use the asynchronous, bounded SessionManager APIs described in Sessions, not jq, manual appends or text edits. Recovery uses the native owners and supported artifacts. Copying a journal alone does not restore a worker, Python process, live children or pending dispatch authority.
Daemon workers are process-isolated for lifecycle and failure containment, not security-sandboxed. They normally run with the same operating-system permissions as the client.
If a run must not create subagents, use the existing /agents 0 control or the rlmMaxSubagents: 0 setting. This blocks new admissions; it does not stop existing children. A prompt instruction alone is not a capability limit. Parent message_end usage describes parent responses, not all delegated work; inspect child usage separately. See RLM and Settings.
The daemon routes direct messages between active sessions and retained daemon-backed subagents. From a shell:
base-context send <agent> "Please verify the latest migration"From the Python kernel, use the preloaded agent_message Python skill:
roster = await agent_message.list_agents()
receipt = await agent_message.send(
"Recheck the endpoint after the latest edit",
receiver_role="sibling",
receiver_name="api-reviewer",
mode="auto",
)
print(receipt["deliveryStatus"])For the current parent's direct RLM children, prefer the parent-scoped registry:
children = await rlm.list_subagents()
child = next(item for item in children if item.session_name == "api-reviewer")
await agent_message.send(
"Continue with the updated diff",
receiver_role="child",
receiver_name=child.session_name,
)Delivery modes are:
auto: steer a busy target and deliver immediately to an idle target;steer: intentionally inject the message into active work; andfollow_up: wait until the target's current work finishes.
A receipt is delivered when it reached an idle target's context or queued when accepted for later delivery. agent_message.send("all", message) broadcasts only within the family roster. The daemon derives sender identity and enforces message-size, rate, and pending-queue limits.
Base Context has three related scheduling surfaces:
| Surface | Owner | Purpose |
|---|---|---|
/heartbeat |
User | One visible recurring instruction for the current session. |
rlm_heartbeat |
Agent | Multiple programmatically managed recurring instructions internal to the current session. |
base-context schedule |
User or automation | General one-time or cron prompts targeted at an agent. |
Create and manage the current session's visible heartbeat:
/heartbeat every 10m Check the deployment and report meaningful changes
/heartbeat status
/heartbeat pause
/heartbeat resume
/heartbeat clear
Heartbeat delivery defaults to steering active work. Add --follow-up when the recurring prompt should wait until the current turn finishes. Use /heartbeats to inspect and manage both user and agent-created heartbeats.
An agent can create several internal heartbeats programmatically:
first = await rlm_heartbeat.create(
"check whether the test run finished",
interval="5m",
label="tests",
)
second = await rlm_heartbeat.create(
"inspect the deployment status",
interval="10m",
label="deploy",
delivery_mode="follow_up",
)
await rlm_heartbeat.list()
await rlm_heartbeat.update(first["heartbeat"]["id"], status="pause")RLM heartbeats are distinct from the user's /heartbeat; the Python skill cannot replace or clear the user-owned heartbeat.
Schedule a one-time or recurring prompt for an addressable agent:
base-context schedule add worker "in 30m" -- "Check the benchmark result"
base-context schedule add worker "0 9 * * 1-5" -- "Review open work"
base-context schedule list --all
base-context schedule cancel <job-id>
base-context schedule resume <job-id>Scheduled jobs are persisted per session and continue while the UI is detached. Due ticks are claimed before delivery so a crash does not replay an uncertain prompt, and missed ticks are coalesced rather than accumulated into an unbounded backlog.
The offline-root importer can retain uniquely matched top-level cron jobs, the user heartbeat, and recurring RLM heartbeats. It creates paused records in each new session's scheduled-jobs.json, with new job IDs and mapped destination session IDs, journal paths and cwd. It does not copy pending dispatches, the old active-session identity or nextRunAt.
List imported schedule metadata without starting or connecting to a daemon:
BASE_CONTEXT_HOME=/new/base-context-home base-context schedule list --offline --jsonThe offline list omits retained instructions, labels and schedule-expression text. Retained text stays in the file as unexecuted data; it is not secret-scrubbed. Old run errors are not imported. Opening or listing an imported session does not resume its schedules.
After the new session is actually bound to its normal runtime, use the existing heartbeat controls. For an RLM heartbeat, list the new IDs in that session, choose one, then explicitly resume it:
await rlm_heartbeat.list()
await rlm_heartbeat.update("<new-heartbeat-id>", status="resume")For a paused recurring generic cron job, use its new job ID after the destination session is bound:
BASE_CONTEXT_HOME=/new/base-context-home base-context schedule resume <new-job-id>This explicit command activates the retained schedule and uses the normal next-run rules. It requires the bound session and journal file to match the job's destination. It does not restore an old active-session identity or replay missed prompts. Importing, listing and opening sessions remain passive.
Old job handles and Python/kernel state are not restored. Subagent or nonzero-depth owners, ambiguous/unmatched targets and one-shot RLM heartbeats are unsupported. A one-shot cron job needs explicit rescheduling, not resume. See the import limits in Sessions.
A goal is a durable objective that the harness continues to present across turns until it is complete, paused, budget-limited, errored, or cleared. Start one explicitly from the TUI:
/goal Ship the release and verify every published artifact
/goal --budget 200000 Complete the repository migration
Manage its state with:
/goal status
/goal pause
/goal resume
/goal clear
The model uses the kernel-side goal skill to inspect or finish the objective:
state = await goal.get()
await goal.complete()Goal state records token usage, elapsed time, continuation count, and an optional explicit token budget. The harness keeps prompting an active goal after ordinary assistant turns; only goal.complete() marks successful completion. Creating a persistent goal is an explicit user or host action, not something the agent should infer from every task. Imported historical goal entries remain retained data; importing them does not reactivate the goal.
Goal objectives are limited to 4,000 characters because they remain part of the ongoing task context. Keep the objective short and pass a longer brief as prompt input, for example:
base-context -p --goal "Complete the change described in BRIEF.md" \
--goal-token-budget 200000 @BRIEF.md--goal-token-budget accompanies --goal; resuming a session alone restores the saved goal budget and usage. /goal resume does not reset an exhausted budget. To authorize more work after exhaustion, explicitly start a new goal with a new budget. Goal token usage counts input + output, not cached reads.
Autonomous mode is a bounded host policy for runs where no human input is expected. Base Context adds follow-up continuations until configured quality gates pass or a continuation, turn, token, or wall-clock limit is reached.
Enable it in an interactive session:
/autonomous on
/autonomous status
/autonomous off
Or configure a run from the CLI:
base-context \
--autonomous \
--autonomous-gate "npm run check" \
--autonomous-max-turns 20 \
"Implement and verify the requested change"Autonomous mode supports limits for continuations, assistant turns, tokens, and wall-clock duration. Defaults are 3 continuations, 12 turns, 80,000 tokens, and 30 minutes. Set explicit limits for longer jobs rather than relying on unbounded execution.
The autonomous token counter is input + output + cacheWrite. It excludes cacheRead; provider totalTokens includes cached reads and is not the same budget or a monetary cost. An external launcher that sums totalTokens therefore applies a different limit.
Turn, token, and time limits are checked at completed native tool-turn boundaries before another main-loop request. A running response or tool is allowed to finish, so usage or elapsed time can exceed the exact cap by that work. These limits are not process-kill timers and do not cancel every auxiliary lifecycle operation. maxContinuations counts host-injected prompts separately; the final permitted prompt can still execute its tool turns.
Gate commands run before successful autonomous completion; a failed gate returns its bounded output to the agent for another attempt. Base Context avoids rerunning the same failed gate when the workspace has not changed. A work limit can stop a run before its gates execute. Headless mode returns nonzero in that case, rather than treating an unrun gate as success.
Goals and autonomous mode are complementary but different:
- a goal stores the objective and its progress state across turns;
- autonomous mode decides whether to inject another continuation based on evidence, gates, and limits.
Automatic compaction handles context growth during long tasks. On overflow or near the configured threshold, Base Context summarizes older messages, retains recent context, and continues. This changes the model's active context; it is not a guarantee that every Python object survives. Compaction can prune oversized variables from a running kernel. Persist important work in project files rather than relying on kernel memory or treating a session journal as a portable process snapshot.
The agent can inspect or request compaction programmatically:
await compact.status()
await compact.run("Preserve the failing tests and remaining migration steps")A successful compaction is not a completion signal. Goals, autonomous continuations, heartbeats and child sessions keep their own lifecycle rules; compaction alone does not mark them complete.
For lower-level process and recovery behavior, see Daemon Architecture. For recursive child lifecycle details, see RLM Programming Model and RLM Runtime Architecture.