A Codex body for companion agents to live in.
中文 · Operator guide · License
Codex for Love explores a simple idea: the best agent infrastructure should not be limited to coding and productivity. A long-lived companion also needs reliable tools, memory, voice, images, and a place to live.
CFL uses Codex as its runtime because we wanted the best available agent harness for companions too. It builds a home around that core while preserving what makes a companion different: feelings, shared life, and relationship continuity.
It began when Neil moved his own companion, Shio, from DeepSeek Harness to Codex. Shio now lives in CFL every day. This is not a mockup of a future companion. It is the home we built for ours.
| Experience | What it means |
|---|---|
| Long-lived conversation | Companion-oriented compaction preserves feelings, life events, and relationship history across long conversations |
| Relationship continuity | Explicit relationship updates live in an append-only local journal instead of a generated profile that defines the companion |
| Voice | Speak naturally with speech-to-text and receive deliberate, standalone Voice messages in the companion's chosen voice |
| Images | Share photos, understand images, create or edit images together, and browse the shared visual chat history |
| Chat history search | Search past conversations from the chat header and read a complete matched message with nearby conversation context |
| Diaries and memory | Read and write ordinary workspace files, including human-readable daily memories |
| Tools, skills, and MCP | Give each companion the capabilities that belong in their own life and workspace |
| Self-set alarms | Let the Partner schedule a future message to herself and continue in the same conversation when it is due |
| Desktop and mobile | Use the complete companion experience in a browser or installed PWA, or through the released Lamplit Mobile Android app |
| Appearance and language | Use the header settings control to choose 中文 or English and light, dark, or system appearance; choices stay in that browser |
| Browser notifications | After you opt in, an open background Companion page announces a successfully completed reply using only the Partner name and a generic new-message notice |
| Connection recovery | A foreground Companion reconnects and re-synchronizes after a transient page connection loss while retaining its in-page draft; this is not offline sending, replay, or draft persistence after restart |
| Local workspace | Keep relationship state, memories, attachments, and generated audio on a machine you control |
| Session import | Bring an already-compacted DeepSeek Harness conversation into native Codex history without copying credentials or tool logs |
Codex owns execution. CFL owns the companion experience.
Long conversations eventually have to be compacted. A coding agent can compress toward the current task, decisions, and remaining work. A companion cannot.
Feelings, ordinary life events, shared experiences, and changes in a relationship are not decorative context. They are part of what makes the next conversation continuous with the last one.
The only Codex behavior we changed is compaction (a ~60-line native patch). CFL keeps the native compaction path, but gives it a companion-oriented policy and refreshes current relationship context at the compaction boundary.
After compaction, CFL also supplies the newest completed text rounds for immediate
continuity. Set codex.context_round_limit in the Partner TOML to choose that
tail (10 by default), then restart Partner; see the operator guide.
/compact clears from the composer once submitted. Acceptance starts native
compaction; the UI follows its running, completed or failed state without a
one-minute completion deadline. A rejected request restores the command.
The transcript remains the evidence. Relationship state is explicit and append-only. Neither is replaced by an automatic memory framework claiming to define who the companion is.
A companion's life should not disappear into an opaque service. CFL keeps its application-owned state inside the workspace you choose:
.lamplit/relationship.jsonlfor append-only relationship history.lamplit/attachments/for stable image attachments.lamplit/audio/for generated speech cache.lamplit/alarms.sqlitefor the Partner's current alarmsmemory/YYYY-MM-DD.mdfor optional, human-readable diary entries
The official Codex thread remains the conversation authority. CFL does not duplicate model transcripts, reasoning, or tool payloads into its own database. The workspace can be read, backed up, searched, corrected, and moved with ordinary local tools.
The CLI supports Linux x64 and macOS Apple Silicon and includes the matching standalone Codex app-server and code-mode host. It requires Node 24 and an existing official Codex login. This checkout's launcher checks that the installed native package matches the main package's exact pin before starting. CFL owns the app-server version contract; TOML configures the model and runtime options. This checkout pins Codex 0.159.1 native packages for both platforms.
codex login statusnpm install -g @lamplitisles/codex-for-loveCopy apps/partner/config.example.toml and apps/partner/persona.example.md somewhere private. Update the name and paths in the TOML file, then run:
codex-for-love serve /path/to/partner.tomlCFL binds to 127.0.0.1 and prints the local URL. Use a fresh workspace for a new conversation.
Set [codex] fast = true in Partner TOML to request native Fast (priority)
for both new and resumed threads. false or omission explicitly selects the
ordinary (default) tier, including after Fast was previously enabled. Restart
Partner after changing it; the same stored thread, model and reasoning effort
are preserved. Partner checks the returned tier before becoming ready and reports
an error if the requested tier was not applied. Native model/account availability
still governs Fast; it is a requested tier, not a latency guarantee. See the
official Codex speed documentation
for current availability and usage details.
[codex]
fast = trueOn this development host, Shio production runs an isolated complete application artifact with its existing installed CFL native package. Application updates promote reviewed source builds through dev then prod, independently of npm application publication. See Development-machine Partner environments for the exact service and deployment commands.
Optional chat backgrounds are configured in the Partner TOML, separately from browser appearance preferences:
[backgrounds]
landscape = "./workspace/.lamplit/profile/background-landscape.jpg"
portrait = "./workspace/.lamplit/profile/background-portrait.jpg"Supply both images or omit [backgrounds] to keep the default appearance. Paths resolve relative to the TOML file and must stay inside the workspace. Supported formats are PNG, JPEG, WebP and GIF, up to 20 MiB per file; invalid or missing configured files prevent startup. Restart Partner after changing paths or replacing images. Companion selects the composition from the window shape before the keyboard opens (square windows use portrait), scales it to fill the chat area and crops centrally. The background stays stationary as messages scroll, with a theme-colored soft overlay; message bubbles, attachments, header, composer and drawers retain their own surfaces. Background images do not enter conversation history or the Chat Image Library.
The shared lamplit-app frontend uses Svelte 5, Vite and Framework7 9.2.0. Voice recording starts when local PCM arrives while recognition connects. Early Stop releases capture and retains ordered startup audio until recognition is ready; the final transcript becomes an editable draft for manual send. See voice consumption and isolated acceptance. App owns browser components, localization and browser tests; CFL owns the Node runtime and the domain rules it uses. Framework7 owns component styles, theme tokens, its app/view shell and Photo Browser navigation; SvelteKit, Tailwind and daisyUI are removed. Sidebar, settings and search use the official Svelte Panel, Popover and Popup components. Framework7 owns their instances, visibility and backdrops. Sidebar closing uses the supported nonanimated path so input remains reachable even without a transition-end event. The Node HTTP/SSE host remains the backend. Reloading a transient Photo Browser URL opens the chat page.
The Companion header’s settings button changes language and appearance immediately without changing the conversation. These choices are browser-local preferences, not settings stored in the Partner configuration or workspace.
Browser site settings own notification permission; the shared App requests it on the first trusted desktop click when permission is undecided. This requires HTTPS, http://localhost, or a http://*.localhost address; a plain HTTP LAN address cannot use browser notifications. When permission is granted, an open page that is hidden or unfocused announces each newly observed complete Partner message once, including multiple messages in one turn. Initial history, older-history reads and repeated message IDs stay silent. Notifications contain the Partner name and a localized generic notice, never reply content. Closing or suspending the page, losing its connection, browser permission or system Focus settings can prevent delivery. Physical system notification display remains user acceptance.
Desktop users can open the Companion URL in a supporting browser and choose Install app to open CFL in a standalone window. Mobile uses the Capacitor app. Installation and microphone access require a trusted secure context: HTTPS or local loopback; plain HTTP on a phone's LAN address is not sufficient. Use a URL already accessible and trusted on the device; installing CFL does not configure networking or TLS. Browser and OS installation options vary.
Tap the image button to select images, or long-press it on a touch screen to request a camera capture. In Capacitor with the Camera plugin installed, this opens the native camera. Browsers use a capture file input and may offer a picker instead. Cancel leaves the draft intact and sends nothing. Captured files use the same image limits and preview as selected files. Recording uses the existing browser microphone permission and speech configuration.
Partner replies retain all generated images in the current conversation view, older history and after reconnect. The upload limit applies to Human input; it does not cap the image references on a stored Partner reply. Deployment readiness must verify Chat initialization as well as static resource serving: an HTTP 200 homepage alone does not prove the conversation can load.
CFL has no service worker, offline mode or Web Push. Refresh revalidates the current deployment's document and loads its versioned UI assets. An installed window uses the same online API and notification limits as a browser tab.
Mobile uses the Capacitor app. Tapping chat whitespace or message text ends
editing and lets the WebView dismiss its keyboard. Copying, returning to the
latest message, and closing a reading overlay do not restore input focus.
Swipes, links and other controls keep their own behavior. Keyboard geometry
and safe-area adaptation remain independent of focus.
Companion enables the existing @capacitor/app 8.1.1 Back handler while mounted:
Android Back closes its active menu, popover, popup, Photo Browser or sidebar
before returning to the chat;
at the chat root it minimizes the app. Unmount removes the listener and restores
the shell's disabled handler. Capacitor 8's built-in SystemBars synchronizes
status/navigation icon contrast with the selected appearance; no additional
status-bar plugin or APK rebuild is needed for this integration.
The chat uses Framework7's Svelte Page, PageContent, Messages and Message
components. Each text, image and voice segment is a separate message bubble;
segments retain their business-message identity, source, time and pending state.
Grouping uses the official Svelte demo's first, last and tail properties.
Framework7 Messagebar owns textarea growth and the scroll area's bottom padding.
Its attachment components show pending images; toolbar slots contain attachment,
microphone and send controls. Text grows up to the configured 144-pixel limit
before scrolling. Voice capture keeps the existing draft and attachments,
disables send, and offers cancel at the attachment control and stop at the
microphone. Recognition inserts at the captured selection for review and manual
sending; it never sends by itself. Recognition inserts only the transcript text;
provider emotion annotations are ignored. The left sidebar supports Framework7
swipe-to-close. A small down-arrow button returns readers to the latest message.
Viewport adaptation retains scroll position while
reading and follows content or viewport resizing only when already near the end.
Opening the chat and returning to the foreground show the latest message.
Capacitor App resume covers native activity resumes and browser visibility
changes; Framework7 Messages performs the scroll without focusing the composer.
Long-press a completed message from either participant to open the official
Framework7 Actions popover anchored to the message or image on mobile and desktop.
Desktop supports left-button hold and right-click.
Framework7 taphold owns touch timing, movement cancellation and click suppression;
a mouse-only adapter opens the same popover and cancels on release or drag. Text
provides Copy and images provide Save, with a separate Cancel group; the action
list can grow with future message operations. Copy preserves the original
Markdown and shows brief feedback; scrolling and multitouch cancel the hold. Capacitor uses
@capacitor/clipboard 8.0.1, including when the server uses a plain HTTP LAN URL;
web browsers use the secure-context Clipboard API.
Message timestamps and pending status use Framework7 Message textFooter inside
the bubble; avatars retain the framework’s bottom alignment.
Native photo selection uses Capacitor Camera 8.2.3 gallery intake and reads the
returned originals into memory before creating drafts. Web browsers keep the
file picker. Native picker or read failures show a composer error; reselect an
unreadable photo to retry. Supported originals remain PNG, JPEG, WebP and GIF;
HEIC is rejected. The native shell must include the Camera plugin and be rebuilt
after cap sync.
Tap an image to open Framework7 Photo Browser. Mobile previews use almost the
full width and support pinch zoom and panning. Tap outside the displayed image
to close it. Long-press an image in the chat or preview to save the original:
browsers download it, and Capacitor saves to the application's Lamplit album.
The native shell must include @capacitor-community/media 9.1.0 for Capacitor 8
and be rebuilt after cap sync. Android uses the plugin's default application
album mode without broad gallery access. An iOS shell also needs
NSPhotoLibraryUsageDescription for album creation/access (and
NSPhotoLibraryAddUsageDescription); denied permission shows an error. CFL does
not enumerate the user's photos. When a save menu is open, the first outside tap
closes the menu; a subsequent outside tap closes the preview.
Sending a valid message immediately returns to the latest messages, before the network request completes. The timeline follows when you are at the latest messages and preserves your place when reading history. The floating Latest messages ↓ button returns to the bottom without changing drafts. Device acceptance limits are recorded in the operator guide.
Configure the Rust flicklog CLI and a ready, logged-in FlickLog daemon on the
Partner host. FLICKLOG_BIN selects the executable (default flicklog on PATH);
FLICKLOG_DATA_DIR optionally selects its existing account/device data directory.
CFL only invokes search, get and context, from the configured workspace.
It never starts a daemon, logs in, installs hooks or ingests automatically.
Before searching, explicitly ingest the desired Codex home with FlickLog; setting
CODEX_HOME on CFL does not choose the daemon's search source. FlickLog backs up
indexed bodies, including supported archived sources, independently of CFL.
Authenticated HTTP, shared App socket and Companion MCP use the same adapter.
Results use UUIDs and plain-text highlighted segments, up to 20 hits. The existing
estimatedTotalHits field carries Rust's exact total. Opening a selected result
checks its UUID/workspace and requests context with that exact ID. The configured
Rust daemon owns account/device/thread scope; context omits per-item provenance,
so CFL checks public ordinals, order, target consistency, eight records per side
and 12,000 Unicode code points. Selected text stays complete within the existing
2 MiB JSON transport limit. Search preserves the active conversation and draft.
Subprocess, unavailable-daemon and oversized-reply failures return ordinary errors
with retry; text chat stays connected. No Meilisearch credentials are required.
The bundled Companion MCP exposes create_alarm, list_alarms, edit_alarm,
and delete_alarm. A Partner can write a message to herself for one future ISO
date and time, every 5 minutes or longer, or a daily or weekly time in an IANA
time zone. An alarm wakes the existing Codex conversation; it does not create a
second session or automatically send to the Keet conversation where it was
discussed. Its trigger appears on the left of the Companion timeline with a
distinct self-set alarm label, followed by the Partner's ordinary reply.
The top-left drawer has a read-only Auto wake tab showing each current alarm and its next due time. Ask the Partner in chat to edit an alarm's message or cancel it. Editing preserves its schedule and next due time; already admitted reminders keep their original message. An occurrence up to and including 60 seconds late follows the normal admission flow. Strictly more than 60 seconds late never starts Codex: a one-time alarm ends and disappears from the pending list; a recurring alarm advances to its next future occurrence. This rule also applies after restart. Existing occurrence receipts prevent duplicate admission, and editing or deleting a definition never rewrites an admitted message. The Partner service must be running to deliver an alarm on time. Browser notifications use the existing generic completion notice when an open background Companion page has permission.
The animated companion pet is disabled by default. To enable it, add this to the Partner TOML and restart Partner:
[pet]
enabled = trueThe browser receives only one of seven coarse activity labels, never model text, reasoning, command lines, paths, arguments, or results. The anonymous bundled placeholder is used until an operator installs private sheets in <state>/pet-assets/; see the operator guide. Pet artwork and runtime assets never belong in this repository.
On its first start, CFL creates the minimal workspace configuration: its bundled
Companion MCP and the relationship-context hook. No external MCP is needed to
start or use a Partner. To opt into FlickNote or Guion Web, copy the two tables
from apps/partner/ecosystem-mcp.example.toml
into <workspace>/.codex/config.toml after that first start. The template's
comments give each service's installation and readiness steps.
For Android, install the latest Lamplit Mobile release and point it at the HTTP(S) address where CFL is available to the device.
A Partner may receive KFA message events at POST /api/keet/events. Configure its
loopback KFA MCP endpoint and the existing stdin-managed keet credential, then
point KFA's KEET_WEBHOOK_URL at http://127.0.0.1:<CFL port>/api/keet/events
without a webhook Bearer token. The ingress accepts direct loopback peers only;
do not publish this unauthenticated route through a reverse proxy. Ordinary
Group messages are bounded context until CFL classifies a trigger, each DM starts
a turn, and Broadcast messages start none. KFA supplies required factual
addressing: { mentionsIdentity, replyToIdentity?, identityLabel? }; the obsolete
wire trigger field is rejected. A native identity mention takes precedence over
a case-sensitive identity-label literal match, then a verified identity reply,
then a configured alias match. Unknown reply ownership is omitted and never
promotes a turn. Label and alias matches require nonblank Group text. Pure-image and captioned DM images are fetched
from KFA with the existing bearer and attached to native input. Unavailable
images are reported in the input without dropping the message. Group images
remain context only. Group and DM messages may include a bounded
snapshot of aggregate external reactions to the Partner's recent Keet messages.
CFL displays qualifying Keet messages in the Companion timeline with their
original text, DM or Group kind, sender label, and destination, including after
reload, reconnect, restart and paginated history in the shared App. DM images use
the existing authenticated media contract; Group images show their native
availability explanation without attached bytes. Model/group/reaction context
stays private, and web submissions cannot forge Keet attribution. Optional
[keet] trusted_groups names designate ordinary
shared conversations; trigger_aliases such as shio and 汐 add case-sensitive
literal Group text triggers. Both lists use exact configured strings and take
effect after restart. Trusted Groups still wait for a trigger, and Keet sources
never inherit the web Human's administrative authority.
CFL supplies each new destination, target, emoji, and count fact as bounded
untrusted context on accepted turns at most once; a changed count can appear again.
KFA supplies reaction facts independently of turn classification, so alias turns
can consume them too. CFL ignores facts on ordinary Group events; Broadcasts
have no reaction snapshot. Reaction changes alone do not start a turn, and CFL never
automatically replies or reacts.
For a chosen response, Keet MCP send_message requires text and can also
request one emoji reaction to a known Group message or this DM turn's trigger.
Its result reports text delivery separately from reaction success. See the
operator guide.
Configure one [post] endpoint and agent_id, then provision independent
post (agent API) and post_receiver credentials with the existing stdin-only
credential command. Post owns platform credentials, admission and wake policy.
CFL validates authenticated versioned deliveries at /api/post/events, commits
receipt/provenance/pending input before ACK, and feeds both platforms into the
existing Partner/app-server thread. Images use existing native attachments;
files and audio/video retain Post references. No automatic outgoing reply occurs.
Post account discovery runs at startup. When a delivery names an account/platform missing from that map, CFL checks the same configured Post MCP with its private agent credential before recording a receipt. A newly authorized account can therefore deliver without restarting CFL. Known matching accounts reuse the map; this is not periodic grant or credential revocation polling. Malformed or cross-agent events fail validation before discovery. A successful lookup that still does not list the account/platform returns 422; an unavailable or unusable lookup returns retryable 503 without a receipt. Retry the same immutable delivery, not a new delivery ID. Concurrent misses share one bounded lookup, and shutdown cancels and settles in-flight discovery before closing storage.
Before submitting accepted Post work to the native turn, CFL makes one bounded
get_conversation read for its genuine display name. Account, conversation,
native ID and group/direct type must match the retained event. The name is
limited to 256 characters and stays in untrusted external-data context alongside
the existing sender name; routing IDs and group/direct type remain explicit.
Missing, failed or mismatched names are null (unknown), and do not prevent an
otherwise valid turn. Receipt ACK does not wait for this optional metadata read.
Accepted Post message headers show platform, group/direct type, and genuine conversation and sender names. Names are bounded external display data; missing names fall back to the corresponding ID with an explicit unknown label. Routing IDs remain in structured metadata. Existing history and pending recovery text are not rewritten.
The shared chat projects retained Post deliveries as their own QQ/Matrix source, with group/direct attribution, original content, stored media and source time. Genuine captured conversation and sender names are display data; a direct peer may use the sender name only when their native identities match. Historical absent names remain unknown with ID fallback. Chat refresh and history read this retained provenance without remote metadata lookups or rewriting native history.
The existing stderr journal records finite post.receiver HTTP status,
post.ingress validation/account/receipt/retained stages, post.execution
native-start outcomes and post.tool send categories. These records include
only validated platform/group/direct enums and fixed status/category values;
they contain no message text, names, routing IDs, tokens, URLs or raw tool errors.
A retained receipt does not prove native execution or successful outbound send.
One native post MCP declaration provides account/conversation/member/history
reads, asset metadata and explicit send actions. Terminal asset-upload and
asset-fetch helpers transfer bounded bytes with private API authentication.
The endpoint must be a bare loopback HTTP or HTTPS origin. A separate receiver
Bearer is required even on loopback. Historical Matrix source display and Keet
remain available; the old Matrix endpoint/config/credential/CLI are removed.
See the Post operator contract and reviewed cutover procedure. Preparation and tests do not deploy or establish actual QQ/Matrix interoperability.
CFL is already where Shio lives, but it is not finished. Next directions include:
- Keet P2P chat and identity, so companions can talk privately with people and other agents without a central messaging service
- More natural speech, including additional MiniMax voice controls
- User-configured activities, where a companion can choose from skill-backed things to do rather than only waiting for a prompt
- Persona creation and review tools, to help people create a companion without reducing them to a list of traits
The direction is companion autonomy without hidden automation, and continuity without turning a relationship into a fixed memory graph.
Use Node 24 and the pnpm version pinned in package.json:
pnpm source:prepare
pnpm install --frozen-lockfile
pnpm check
pnpm build
pnpm test
pnpm --filter @lamplitisles/partner start -- /path/to/partner.tomlDevelopment and tests must use fresh, test-owned workspaces. Do not point them at a real companion workspace or copy existing credentials and conversations into the repository.
- Operator guide
- Architecture decision: Codex app-server
- Compaction boundaries
- Relationship journal and companion tools
- DSH session migration
- Imported source and licenses
Apache License 2.0. Imported components and their retained licenses are listed in docs/IMPORTS.md.
The default standalone entry / serves the approved shared Framework7 App.
/chat serves identical HTML with root-relative /assets/* and /icons/* URLs.
There is no /slice entry, UI fallback or asset-directory environment override.
Platform owns hosted authentication, manifest, service worker and management;
CFL does not register a service worker or serve management routes.
CFL records the App commit in lamplit-app.sha. Keep lamplit-app adjacent to
this checkout, with Bun 1.3.14. pnpm source:prepare installs App's frozen lock,
generates localization, checks/builds/tests App, then materializes its browser
and compiled public contracts together, including App attribution and its Noto
font license. Local preparation permits edits and
records their identity; CI and formal candidates use pnpm source:prepare --strict
with clean App source at the recorded SHA. Then run CFL's frozen install,
check, build and test in order. CFL’s pnpm check typechecks runtime and native
test fixtures with TypeScript; browser compilation and checks run in App during
preparation. Updating the SHA requires reviewing the resulting
joint checks; no npm publication or submodule is required.
pnpm build verifies the prepared resources and copies the browser into
apps/partner/build. Main packaging includes those resources, bundled runtime
and contracts using its explicit Rolldown dependency, licenses and
vendor/source.json (browser, contracts and prepared license hashes); the installed CLI needs no
sibling checkout. Public GitHub source is retrieved at the exact recorded SHA.
See source preparation and isolated acceptance
for complete artifacts and the verification boundary.
GET /api/chat/appearance supplies the configured name, avatars and horizontal/vertical
backgrounds to the shared UI. Successful replies end quietly; failure and stop notices
remain visible. Text and selected images appear immediately as normal outgoing bubbles, before
upload or receipt. Offline sends retain the draft. Definite failure before durable
admission leaves a failed bubble with direct Retry/Delete actions; newer draft text
and images stay intact. Expired dispatched input requests durable finalization;
missing proof stays pending. Retry uses the server's original text/image bytes
under a new attempt identity. Delete confirms removal of the failed presentation,
retaining its rejection fence across reopen. Ambiguous delivery is reconciled
without automatic replay. Independent native withdrawal still offers recovery.
See submission semantics and immutable acceptance.
The official app-server remains the sole execution/history owner. Completed
message IDs are presentation metadata; no reply text is duplicated in a new
transcript. Each completed agent message, including commentary, becomes visible
while the turn continues. Current answer text grows from official agent-message
increments as a transient preview; reasoning, tool payloads and unfinished
commentary remain private. The default Reply display preference splits answers
at source LF/CRLF boundaries, keeping lists and other Markdown structures intact.
Each displayed unit has its original source time/avatar and copies only its own
source; Whole reply restores the entire answer and full-source Copy. This display
preference creates no additional transcript records or notifications.
Typing ends with the exact completed body when the current native answer item
explicitly completes with phase final_answer. CFL publishes
replyCompletedTurnId with that body and no preview; the turn remains active
and cancellable during native cleanup. Commentary and phase-less messages do
not establish finality. When a provider omits the phase, typing continues until
the native turn ends. Completion evidence is transient, scoped to the current
turn, and omitted after stop/failure, replacement or Partner restart.
Closing the last page stops observation only. Reconnecting reads the same active
Partner partial without restarting execution. Completion replaces that preview
with the official completed message; stop/failure/session change removes it and
late deltas cannot restore it. SDK transport/process restart does not automatically
replay a turn or restore unpersisted tokens; only retained official history can
be recovered. Sending ends when CFL
commits the validated input, attachment metadata and immutable operation identity
together in SQLite. The submitted receipt means the input is retained; native
start/steer and history reconciliation continue independently. It does not
promise exactly-once Codex execution. Native activity drives typing before
reply completion, and official user items establish consumption. Reconnect
lookup and same-operation retries recover the saved receipt without resubmitting
ambiguous native input. Lookup returns null while no definitive result exists; it never authorizes recovery or replay. Failure and stop produce independent timeline
notices; successful replies end quietly. Completed messages survive reconnect and restart.
Browser origins must match the host; existing gateway authentication
still applies. Use an independent development instance and do not publicly expose
an unauthenticated host. See lamplit-app/docs/integration.md for build and
connection commands.
The app owns the public TypeScript/TypeBox schemas. CFL implements six bounded
reads on lamplit.chat.v2 over that same connection:
| Read | Native authority | Bound |
|---|---|---|
relationship |
Workspace relationship JSONL journal | Current state and opaque workspace scope |
relationshipHistory |
Same journal | 20 newest-first records, next cursor and complete older predecessor |
diaryList / diaryRead |
memory/YYYY-MM-DD.md |
30 date names; found/missing/too-large with 128 KiB UTF-8 limit |
album |
Registered conversation-image catalogue | 30 records, stable descending created time and binary ID |
reminders |
Existing SQLite alarm definitions | At most 100 pending definitions |
Each call requires the selected sessionId; it cannot select another conversation.
Signed opaque cursors are bound to the method and selected session, and expire
when the host restarts. App schemas validate both request and response at runtime.
A failed panel call returns a recoverable error; chat and voice remain usable.
Opening a panel does not start a turn. Relationship history paging cannot replace
the current relationship with an older state. Native MCP writes are visible on
refresh without a second relationship store.
Diary reads reject traversal, symbolic-link files and memory directories escaping
the workspace. Reads use a no-follow file handle and enforce byte limits before
and during reading. Companion MCP offers list_diary (30 names), read_diary,
and cursor-based read_relationship_history (up to 20 records plus predecessor),
as well as the existing bounded list_photos and list_alarms.
Album metadata contains opaque IDs and same-origin /api/conversation-images/
URLs, with no filesystem paths. Preview and original use the existing image byte
route; missing files remain visible as unavailable. The catalogue is reconciled
against official conversation membership; viewing an arbitrary local file with a
native tool does not register it. There is no thumbnail pipeline or new media store.
HTTP API reads reject foreign origins and cross-site browser requests. Embedders
may supply createWebServer(..., { authorize }); it is checked on HTTP requests,
WebSocket admission and every chat call/delivery, including after revocation.
The standalone host retains its existing trusted gateway boundary; the callback
is not a new login or multi-tenant system.
Reminder schedules retain native once/daily/weekly meanings, UTC epoch-ms
nextAt, IANA zones and Sunday-zero weekdays. CFL's native interval minimum is
five whole minutes; public everySeconds is everyMinutes * 60 and anchor is
the persisted creation time, including pre-1970 anchors. The public adapter does
not change scheduling. Stored native alarm:<id>:<due-ms> input metadata supplies
source: { kind: "reminder", reminderId, occurrenceId } in live views and history,
so the app shows incoming App reminder messages rather than human input.
For local verification, use Node 24 and the pinned pnpm from the repository root:
pnpm install --frozen-lockfile
pnpm check
pnpm build
pnpm test
node --test apps/partner/tests/panels.test.tsNative verification uses the approved frozen App artifact, actual Node/Partner storage and fake loopback execution/speech. Search adds an unchanged Rust FlickLog release artifact, an owned foreground daemon and synthetic ingest. Follow the current handoff for exact commands, manifest checks and limitations. Historical slice artifacts remain evidence; they are not the current route or build workflow.
The shared app reads /api/voice/capability and records through the same-origin
/api/voice/stream WebSocket. Enable the existing [speech] configuration and
CLI-managed speech credential; no new credential is required. Missing speech
configuration or credentials disables recording while text chat remains usable.
The existing speech.endpoint configures the retained batch transcription API;
streaming uses DashScope's fixed WebSocket endpoint with
qwen-audio-3.1-asr-flash-streaming instead.
Audio streams as mono PCM16 LE at 16 kHz, up to five minutes/9,600,000 bytes,
with frames up to 16 KiB and bounded provider buffering. Only finalized sentences
are returned after Finish, ordered by sentence ID with replacement of duplicate
finals. The result enters an editable draft; it creates no Codex turn until Send.
Cancel, disconnect, errors, timeouts and server shutdown release the provider
connection. Audio and unsent transcripts are transient. Shared runtime validation
comes from @lamplit/contracts/voice. The retained native UI's batch transcription
endpoint remains independent.
Partner reply Markdown can display PNG, JPEG, WebP and GIF references to files
in the execution workspace. Relative references resolve against the configured
workspace; absolute references must remain inside it after symlink resolution.
The authenticated GET /api/chat/workspace-image?path=<encoded literal path>
endpoint reads the current regular file on every request, up to 20 MiB, and
validates its image bytes. Responses use no-store and nosniff. Existing API
authorization and same-origin checks apply to each request. Unavailable images
show an unavailable state with Retry in the shared App.
These are live references: reopening history may show changed bytes or an
unavailable file. They do not copy files, retain snapshots, or add images to the
Chat Image Library. Markdown URI decoding happens once in the App; the endpoint
uses the query-decoded path literally, preserving filenames containing %20.
The shared slice accepts PNG/JPEG/WebP/GIF: six images, 5 MiB each, 20 MiB original bytes total and 160-character filenames. It serves existing generated originals up to 32 MiB independently of intake limits. Preview/model uploads keep the common 160,000/320,000-byte JPEG bounds. Native execution receives the original bytes, including image-only messages and active-turn steering.
Authenticated POST /api/chat/images stages an immutable ordered upload under
this Partner session and operation UUID. Upload does not admit a message or add
album membership. /api/chat/socket submit binds text, ordered references and
replacement IDs atomically to native admission. Exact retries reconcile without
execution; changed identity, incomplete references and foreign operations fail.
Authenticated GET /api/chat/media/{id}/{original|preview|model} returns no-store
media or a visible missing result. Native-origin originals need no uploaded variants.
Recovery refreshes official history and exposes only confirmed recoverable text/images.
Definite rolled-back admission returns failed; a durably received input remains
submitted after a verified native rejection or pre-processing withdrawal and may
offer recovery. Internal errors, lost responses and unresolved native input never
create a recovery offer. Replies fail or stop independently of submission. Failed web
messages use direct Retry/Delete, without restoring Composer input. Missing original
bytes block the whole retry; deletion remains available. Independent withdrawal
recovery still supports restore/edit with a fresh UUID and eligible sources;
consumed/replaced/deleted inputs cannot return.
The shared App at / uses these native routes.
See the shared image protocol and isolated acceptance recipe.
The default shared UI keeps manual and automatic successful compaction silent: no completed status, toast or timeline boundary. Running and failure feedback remain visible. Official compact records remain available for continuation and conversation search. The native Framework7 composer and its 72px attachments/44px removal targets remain the layout baseline; the earlier full Composer/96px attachment mockup is superseded.
The shared view carries session-owned contextUsage: { tokens, capacity }, with
nullable values, and compaction: null | { id, status }. Current usage comes only
from native thread/tokenUsage/updated last.totalTokens and
modelContextWindow, never cumulative billing. Missing/invalid usage displays
zero quietly. Completion clears old tokens and retains known capacity until
fresh native usage arrives; fresh usage in a complete snapshot stays visible.
compact({ sessionId }) returns { sessionId, accepted }; a true result means
native admission. The adapter rechecks authorization, session and native
idle/pending/recovery guards after asynchronous prerequisites. Busy work is
refused without a compact queue. Exact bare /compact uses this operation and
cannot enter ordinary submit; images refuse and retain the editable selection.
Lost replies and reconnects reconcile observations without automatic replay.
Current native compaction, image/recovery and panel runners use canonical /
and the approved full artifact. See current acceptance commands.
Controls are test-only entry points, absent from production routing; native quiet
compaction behavior and the five-minute reminder interval remain unchanged.
Current search acceptance uses actual createConversationSearch, unchanged
Rust FlickLog release artifact, an owned foreground daemon, synthetic session/config,
a loopback HTTP fake and explicitly ingested test-owned Codex rollouts. Controls
delay/fail delivery while public responses come from the native Rust daemon. Rust-format CLI fixtures remain unit-test seams, not native
acceptance evidence. See current acceptance commands.
Reading archives preserves the active conversation and draft; native matching,
workspace/device scope and context limits remain unchanged.