Imported from Risingtides-dev/ocean-os (
crates/ocean-agent/AGENTS.md). Install upstream withnpx skills add Risingtides-dev/ocean-os --skill ocean-agent. Copyright stays with the author.
ocean-agent — Session and Prompt Layer
Purpose
This crate owns Ocean's agent session/history layer and project prompt loading. Session load/save bugs here affect both the TUI and ocean-surface because clients depend on the daemon remembering transcripts by session id.
Ownership
- Scope:
crates/ocean-agent/ - Parent contracts:
../../AGENTS.mdand../AGENTS.md - Primary responsibilities: session persistence, workspace binding, GC, and transcript projection in
src/session/mod.rs; runtime/history shaping insrc/lib.rs; system/surface prompt assembly, project instruction discovery, and prompt-memory context insrc/system_prompt.rs
Local Contracts
- Preserve session compatibility unless a migration is documented.
- Session-config model pins update model/provider together under the same
per-session lock as turn persistence and increment the persisted monotonic
config_revision; explicit creation may atomically seed one already-resolved model/provider pair at revision one, while legacy session files deserialize that revision as zero. Detail and bounded sync projections carry the same revision. Optional config reads must distinguish an absent session from unreadable/corrupt storage so daemon adapters map only genuine absence to 404. - Session model-pin provenance uses the existing monotonic
config_revision: a positive revision proves an explicit config mutation, while revision zero (new inherited or legacy) retains the former model-difference fallback. The sharedSessionModelConfigresolver is the sole authority for config projection and turn selection; global-model equality must not erase a pin. - Permission-mode persistence atomically writes the authoritative three-state
file and reports write failures. Load old booleans as automatic/skip-all; the
legacy
yolo_prefis a best-effort downgrade mirror, while current boolean reads derive from the authoritative mode so the two cannot disagree live. - Project instruction discovery must respect the repo devlog chain: repo-root
AGENTS.mdis the root contract;.ocean/AGENTS.mdis only a child doc for.ocean/runtime artifacts. agentdir::resolve_snapshotparses folder-agent runtime fields only from the caller-owned immutable relative-path byte map and never reopens the live tree. Filesystem authority remains with the caller: security-sensitive consumers must capture through confined handles and derive package identity from the same map they pass to the parser.AgentRuntime::config_dir()is the read-only daemon authority captured at construction; daemon-owned adapters must use it instead of re-reading process-global config environment during requests.- Do not add new instruction sources without tests proving ancestor/nested cwd behavior.
- Project ownership resolution must compare canonical roots after the cheap exact
lookup and when mapping a linked worktree's Git common directory back to its
main checkout; path aliases such as macOS
/varand/private/varare the same authority boundary, not project-less sessions. - Turn persistence is incremental: save the accepted user message before provider execution, then save only at provider-valid round boundaries where every assistant tool call has its ordered tool result. Never persist an orphan tool-call batch.
- Spawned agent loops must remain owned by the parent turn future. Dropping the parent must abort the child; Tokio's default detached-on-
JoinHandle-drop behavior is unsafe for side-effecting tools. - Pre-stream provider failover must pin one session id and hold one per-session turn lock across the complete primary/fallback transaction, reusing the primary attempt's durable accepted-user row; never allow an intervening turn, append the operator prompt twice, or orphan an acceptance-only session.
- Track-0 room prompt guidance is retired; prompt assembly must not infer a closed room role from agent-turn input.
rooms::RoomRegistryis the dormant in-memory twin ofocean_store::SqliteRoomStoreand owes its markers the same filter: a join/leave body quotes a caller-supplied display name throughocean_core::bounded_proseunder this module'sMARKER_FIELD_MAX_CHARS, which matches the store's deliberately. Having no live caller is not a filter — it is why the twin kept an unboundedformat!for a release after the durable side was fixed.- Desktop Surface guidance is exclusively
surface-tauriand uses the shared Leptos component contract; do not add parallel desktop prompt families. - Persisted history search reads only display-projected user/assistant transcript text; it must never inspect tool payloads/raw provider messages or invoke providers/embeddings. Preflight cumulative raw session-file size against the 64 MiB request budget, then enforce the same cumulative bound while reading so concurrent replacement/growth cannot bypass it.
PromptControl::without_tools()is the fail-closed no-capabilities boundary. Empty or unmatched folder-agent allowlists intentionally remain fail-open and must never represent a no-tools posture.- Per-turn memory authority is one exclusive
PromptMemorymode: ordinary operator memory, disabled memory, or one opaque admitted-Room handle. The process-wide memory factory backs both the ordinary provider and Room issuance; onlyAgentRuntime::admit_room_memory(&impl RoomMemoryAdmission)may mint the latter, andAdmittedRoomMemoryexposes no raw room key, partition, owner setter, or serde path. Room turns remove every registryretain/recall, apply the immutable ambient capability intersection, then append only the fixed room-scoped pair;without_tools()remains stronger and appends nothing. Room prompt assembly injects no operator facts and names only the Room namespace. - Durable Room transcript retrieval uses the separate opaque, non-Serde
AdmittedRoomHistoryauthority. OnlyAgentRuntime::admit_room_historymay mint it from final typed admission evidence plus a daemon-ownedRoomHistorySource; the fixed Room/agent/generation scope reaches that source on every bounded backwards page and never enters tool arguments.room_historyis reserved against ambient providers, appended only after the immutable capability intersection, advertised only when the handle is present, removes ordinary operator memory when attached, and is removed bywithout_tools(). PromptControlreceives exactly two effective harness-profile booleans from the daemon:hashline_editsandartifact_spill. Direct/legacy callers default both off; do not add declarative profile fields here until production runtime composition actually consumes them.- History shaping preserves stored thinking only when the selected route is exact
kimi/kimi-k3(Moonshot requires same-modelreasoning_contentreplay) oropenai-codex(the codex encoder replays its own marker-signed encrypted reasoning items and MUST receive them back — stripping them degenerates gpt-5.x into malformed tool calls across tool rounds). Kimi K2.x and other OpenAI-compatible routes retain the existing thinking-strip boundary; provider encoders still drop cross-provider thinking. - Public
SessionTranscriptEntry.textand persisted history search project visibleContent::Textonly. ProviderThinkingremains in raw persisted messages for compatible same-provider replay and never enters display/search text. - The shared session mutation mutex exposes an opaque operation lease for daemon admission. Interactive product/legacy/call turns and config/message/ compact/sync routes acquire it non-blockingly before lifecycle or mutation; durable room turns wait on the same lane after their durable queued footprint so an acknowledged trigger is never dropped. Every leased turn retains the lane through persistence and terminal/invalidation publication. Plain runtime wrappers remain compatibility callers that acquire the same lane.
SessionSyncSnapshotis projected directly from persisted messages without constructingSessionDetail: user/assistant visible Text only, fixed image placeholder with no metadata, at most 512 rows and 1 MiB text, with explicit front-row/text truncation counts. Tool rows, raw messages, tool context, provider thinking, image bytes, and MIME metadata never enter this response.compact_sessionis owned here: one-shot no-tools model call, atomically replaces session transcript with summary + protected recent window. The session lock must be held for the entire load-call-save cycle. Only the current-runtime model is used; session-historical model is ignored. The protected window keeps at most 20 messages and at most 20% of the context window (always the newest message) and never begins on an orphan tool result. A fully-protected transcript is anok:trueno-op with no model call. Provider readiness fails closed before the call; the call is bounded by the 300-second turn budget; every failure path (not-ready, provider error, timeout, empty summary) leaves the stored transcript untouched, and corrupt storage is anErr, never a wipe.
Work Guidance
- Keep prompt-loading behavior deterministic and easy for cold agents to reason about.
src/system_prompt.rsis one intact cohesion boundary. Prompt wording and literal bytes are behavior; do not mix wording changes with structural extraction.src/session/mod.rsis the intact persistence boundary. Do not split it or change schema, atomic-save order, duplicate healing, or resume behavior without a separately approved design and compatibility tests.- Avoid client-specific assumptions; daemon, TUI, and surface clients share this session layer.
- Refresh the recorded
cwdon every bind; updateworkspace_rootand git metadata when the caller moves into a different workspace. - When changing prompt text, include tests for client-type differences when relevant.
- The TUI fallback/profile guidance must advertise its supported terminal component
projections and distinguish them from unsupported arbitrary web/HTML layouts;
never restore a blanket
component_renderban while the TUI consumes those events. - Keep the base prompt compact and tool-agnostic: runtime tool schemas describe mechanics; the prompt governs selection, batching, and verification.
- Memory guidance must not encourage unconditional recall. Call
recallonly when prior conversations, preferences, or decisions are needed and not already injected.
Verification
cargo test -p ocean-agent system_promptcargo test -p ocean-agent sessioncargo test -p ocean-agent project_prompt_loads_ocean_agents_md_from_ancestorcargo test -p ocean-agentcargo check --workspace
Child devlog Index
No child boundaries defined within ocean-agent/ at this time.
room_resources.rs(Rooms Phase 2d) owns the admittedroom_list/room_readtools andconfine(path confinement on the canonical RESULT, re-exported asconfine_room_resource_path). The shape isroom_history.rs's:AgentRuntime::admit_room_resourcesmints an opaqueAdmittedRoomResourcesfrom admission evidence (RoomResourceAdmission= the history admission trait), a daemon-ownedRoomResourceAuthority, and a display-only catalog;PromptControl::with_room_resourcesattaches it andapply_admitted_room_toolsstrips the two reserved names from every ambient provider before appending. The authority is consulted on EVERY call with the fixed scope and answers a typedRoomResourceError; model arguments carry only aresource_idand a relative path. Budgets are Gate 0 Decision 8 (2,000 entries non-recursive; 8 MiB per file; 64 KiB default / 512 KiB max chunk withnext_offset; 30 s deadline); a NUL-bearing file isbinary_not_supported. Every call, refused or not, hands the authority oneRoomResourceAuditFactwith a path digest and never path text or content.