The current product identity is CodeCarry. It is an independently maintained
fork based on OC Remote; the Android namespace/applicationId is
dev.wuxie233.codecarry, and the canonical repository is
https://github.com/Wuxie233/codecarry.
- CodeCarry is a single-module Android application in
app/, built with Kotlin, Jetpack Compose, Hilt, Ktor, coroutines, and kotlinx serialization. ServerTypeis the backend boundary:OPENCODE,DSH, andCODEXhave separate transport contracts and capability routing. Codex returned in 1.12.0. Pi Stack and Pi Roundtable remain removed; persisted rows of those types drop on DataStore read.- OpenCode uses REST for snapshots and commands plus SSE for live state.
OpenCodeConnectionServiceowns connection continuity,EventReducerowns the server-scoped live aggregate, and screen ViewModels derive UI state and coordinate user actions. - DSH uses Typert Remote:
POST /api/<namespace>/<method>with{ type, rpcId, method, payload: { args } }, plus one downlink WebSocket/api/remote.muxwith logical streams.DshApiClientowns unary RPC and$events/result,DshConnectionManagerowns generation readiness and reconnect,DshEventReducerowns mux aggregates. Ready only after cookie exchange, mux open,$eventsready (host.home),session/controlopening baseline, andworkspace/followopening baseline. Contract:docs/specs/dsh-remote-auth-1.11.0.md. - Codex uses native app-server JSON-RPC over WSS, with
CodexConnectionManagerowning reconnect/generation boundaries andCodexEventReducerowning live state. Its dedicated thread/chat screens route byserverId. Do not send Codex through OpenCode REST/SSE or DSH RPC. codex-bridge/forwards authenticated WebSocket frames to the existing shared daemon Unix socket. It owns connections, never agent processes. Terminal clients must share that daemon (codex --remote unix://) for live steering. A separate WebSocket app-server or standalonecodex execis a different live runtime. Details:codex-bridge/README.md.- When WSS connects but model turns time out, inspect the shared daemon
process environment. Shell proxy exports do not reach a systemd daemon;
configure its owning unit. See the outbound proxy section in
codex-bridge/README.md. - A newly started Codex thread has no durable rollout before its first turn.
Keep its start response on the same client connection; clear that cache on
first submission or connection failure. Thread lists set
modelProviders=[]to avoid hiding sessions when the default provider changes. Catalog requests also explicitly enumeratesourceKinds: omitted or empty defaults to interactive sources and excludes subagents. Event-only thread accumulators havehasMetadata=false; keep their deltas but exclude them from navigable topology until a thread snapshot supplies metadata. Internal background tasks may emit events without leaving a resumable rollout. - Compose screens own presentation. Keep navigation, transport, reducer, and backend-specific state outside reusable UI components.
- Route backend behavior through
ServerTypeand explicit capability models; do not infer protocol compatibility from similar UI features. - Scope OpenCode reducer reads and mutations by
serverIdbefore joining on session, permission, question, or parent IDs. - Keep REST snapshots merge-safe with live events. A late snapshot may fill missing history, but must not overwrite newer SSE-derived state.
- Put JVM tests in
app/src/testand device/gesture coverage inapp/src/androidTest. Prefer focused tests for a changed state machine, then run the repository verification commands below once after integration. - Keep user-facing strings in Android resources and preserve the existing Compose component boundaries when changing chat or session layouts.
- Prefer concise Chinese for user-facing updates and final summaries.
- Keep code symbols, commands, file paths, version tags, and API names in their original spelling.
- Do not ask code-level questions. Make technical decisions from the codebase; only ask when product behavior or user intent is genuinely unclear.
- The user prefers
vibe talking: natural chat-first UX, minimal visible control panels, and no unnecessary explanation.
- Releases are manual-only and should follow
README.md: bumpversionNameandversionCode, addRELEASE_NOTES_<version>.md, verify locally, pushmaster, create/push tag, then manually trigger.github/workflows/release.ymlwith the tag. - Tag pushes alone do not publish releases for this repo.
- Use
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64for local Gradle verification in this environment. - If Gradle/Hilt/Kotlin generated cache errors appear, run a clean build/test before treating them as code failures.
- Do not restart OpenCode services or processes from this repo work.
- Do not force-push or clobber existing tags unless the user explicitly requests it.
- Keep
.codegraph/,.kotlin/, and local OpenCode cache/config artifacts out of commits.
-
Native OpenCode send readiness is independent of full REST history and unrelated screen initialization. A usable OpenCode connection plus an available route directory is enough to start draining sends; do not wait for delayed session metadata or project sync once that directory is known.
-
Early native OpenCode submissions are captured in a per-chat, in-memory FIFO. Additional submissions may append while a queue exists. Remove only successful heads and drain in order; later entries must not overtake the head.
-
If the FIFO head fails, keep it at the head with visible retry semantics. Retrying resumes the same queue; it must not duplicate a successful send or silently discard the failed request.
-
The native pending-send FIFO is ViewModel memory, not durable storage. It does not survive process death; persisted composer drafts are a separate feature.
-
OpenCode message history remains a limit-only REST contract. Loading older messages raises the limit and refetches a larger full page; there is no cursor or incremental-page API to document. Render reducer messages already available while that request or other initialization remains in flight.
-
Merge a restored OpenCode REST page into the current reducer state rather than replacing it. On conflicts, the current live message/part state wins so a late REST response cannot roll back newer SSE deltas.
-
DSH is its own transport. Do not reuse OpenCode REST/SSE models. Unary envelope is
{ type, rpcId, method, payload: { args } }on slash Remote paths. Live state rides/api/remote.muxwith logicalopen/item/endstreams. Reconnect reopens mux and re-follows$events,session/control, andworkspace/follow. Per-chat history usessession/followplussession/page. Both take a HostSessionAddress: ordinary sessions{ kind: "session", sessionId }; a child withorigin == "subagent"{ kind: "subagent", parentSessionId, childSessionId, mode }. Never follow/page a subagent origin askind: "session"— Host returnssession/agent-busy. If origin is subagent and parent is still unknown, wait; do not invent a parent.session/pagethroughSeq is the follow snapshot cursor (later live seqs), never a JS sentinel; unknown cursor skips the page call. Chat send/stop on that child usesubagents/promptandsubagents/interruptByParent. No SSE fallback.host.describe,/api/events.mux,/api/events.host,/api/respond, andworkspace.listare gone. -
DSH
assistant/messagealready embedstool-callblocks. A later muxtool/callwith the samecallIdmust update that part, not append a second one, or Chat renders two identical Shell/MCP rows. -
DSH connect is HTTP(S) only. Latest DSH requires a process launch token:
GET /?token=mints an authority-bound Connection cookie. CodeCarry acceptsServerConfig.tokenor?token=on the saved URL (strip the query after save), follows the 303, and keeps the cookie in the in-memory generation. Shared Ktor stays cookie-free; DSH attachesCookieper request. OptionalServerConfig.passwordremains HTTP Basic for a fronting proxy such as dsh-auth. A passworded public host still GETs/so the proxy can attach the current process token, and is treated as loopback because that proxy rewrites Host to127.0.0.1:18790. Passwordless non-loopback URLs still hide:directoryPicker/pick,session/openWorkspacePath,credentials.*,settings/openSettingsDocument,llm/discoverModels,agentPresetsread/copy/openDocument/remove. A 401 isDshAuthRequiredException. -
Answer DSH
approval/requestanduser-questions/requestwaterfalls onPOST /api/$events/resultwith{ args: { clientId, eventId, outcome } }. Question answers must echo each questionidand put custom text incustom, never inselected. Single-select custom and selected labels are mutually exclusive. Approval outcome isallowed-once|rejected(map Always to allowed-once). A rejected receipt must surface an error and unlock the card. Pending requests survive screen collector timing and clear on disconnect or resolved frames. DSH approval has no Always grant. -
session/promptmode isqueueorsteerand requires a client-mintedrequestId. A sole text block starting with/is a host slash command. DSH file mentions usedirectoryPicker/list, not OpenCode@filesearch. DSH has no shell/terminal. -
Remaining non-loopback DSH unary surfaces (workspace, skill, git, preset list/select, goal, automation, settings mutate, llm catalog, subagent, systemPrompt, directory browse) live under Server Settings via
DshHostSurfaceController. -
DSH Sessions + opens the in-app
directoryPicker/listbrowser, not the loopback OS picker. Selecting a directory runsworkspace/createthen reuses an unarchived blank member orsession/create(workspaceId). No Repo omits bothcwdandworkspaceId. Do not create with a barecwd; that leaves the session Ungrouped. Planner:DshConnectWorkspace.kt. -
Chat markdown has two render paths: normal markdown uses Compose in
ChatMarkdownRenderer.kt; KaTeX/math markdown uses the WebView renderer inMarkdownMessageRenderer.kt. Keep code blocks, tables, display math, and reasoning/plain text with long unbreakable ASCII tokens independently horizontally scrollable; normal prose should still wrap to the bubble width. -
Chat Markdown has one structural pipeline:
MarkdownDocumentParser.ktbuilds the GFM AST-backed document,MarkdownRenderPlan.ktassigns block routes and structured table data, andMarkdownStreamingPlan.ktextends or reparses only the open suffix. Do not add a second line scanner or whole-message/max-chunks fallback. -
Streaming chat must not replan unchanged earlier messages.
ChatMessageRowPlanningStatecaches rows by message fingerprint;reuseStableChatMessageskeeps earlierChatMessageidentities so Compose keys stay put. DSH live folds go throughDshHistoryFolderand only apply new seqs. -
Long assistant Markdown is split into typed top-level rows. Tables and fenced code own their rows; root lists, blockquotes, raw HTML, and indented code remain atomic; prose may coalesce within the size budget.
-
Compose rendering dispatches planned blocks independently. Tables always consume structured cells through
MarkdownTableLayout.ktwith 80..280dp content-adaptive columns; only a non-table block containing math routes through the KaTeX/WebView adapter. Keep prose selection intact: JVM gesture diagnosis provedSelectionContainer + DisableSelectiondoes not block table dragging. -
Compose ordered lists use a local renderer because mikepenz 0.28.0 resets every independent list to
1.. Preserve eachORDERED_LISTAST start number and recompute nested list starts independently. -
Compose blockquotes use a local renderer because mikepenz 0.28.0 renders only the first paragraph child. Preserve every quote child in source order, including later paragraphs, nested quotes, lists, code, and tables.
-
Kimi Code Web layout research is pinned to the last source snapshot
e7d5a0aindocs/research/kimi-code-web-layout.md; currentkimi-codemain keeps onlyapps/kimi-code/dist-web. Reuse its information architecture and interaction contracts, not Vue code, CSS, branding, fonts, or generated bundles. -
Cursor is not a third OpenCode-like HTTP server. Official control surfaces are the Cloud Agents REST API (
api.cursor.com), CLI ACP (agent acpover stdio JSON-RPC), andcursor-sdk-bridge(sdk.v1Connect on loopback). Cursor for iOS already uses the cloud/remote-control path; Android is planned with no date. Product preference if CodeCarry adds Cursor later: local agent via a host sidecar, not cloud VMs and not IDE CDP. Do not reverse-engineer IDE protobuf or OpenAI-compatible private-endpoint proxies (ToS §1.5; staff have said that path can ban accounts). Feasibility notes:docs/research/cursor-control-surface-feasibility.md. -
First-table drag coverage now spans Compose tables, long planned Compose rows, KaTeX/WebView tables, and a user-style
SwipeToDismissBoxparent inMessageMarkdownHorizontalDragTest.kt; all 16 physical-drag tests pass on the task AVD. A field failure that remains after these paths pass needs the exact message payload and input context before changing production gesture arbitration. -
Connection setup is multi-stage.
OpenCodeConnectionService.connectionPhasesmust reflect the real health check, workspace/session sync, activity restore, live-event setup, and retry wait; Home should render those phases in the existing server card instead of reducing them to a generic spinner or fake percentage. -
Response-ready notifications use
SessionNotificationIdentity. WhenChatViewModelmarks an OpenCode session read, it must cancel that session's response notification and remove the server group summary only when no sibling event notifications remain; permission, question, and error notifications stay independent. -
EventReducer.sessionsis a cross-server aggregate. Any Home or chat derivation that follows parent/child session relationships must first restrict IDs throughEventReducer.serverSessions[serverId]; OpenCode session IDs and parent IDs are not safe cross-server join keys by themselves. -
Parent/child topology must come from
EventReducer.serverSessionDetails[serverId], not from the globalsessionslist filtered by IDs; another server may reuse the same session ID with different metadata. -
Pending OpenCode permissions and questions retain ownership in
EventReducer.permissionsByServerandquestionsByServer. Consumers and optimistic removals must select the activeserverId; duplicate session or request IDs across servers must never share or clear pending state. -
OpenCode recent work belongs inside the selected server's Sessions control surface and must be derived from that server's
serverSessions[serverId]; do not place a cross-server recent-work feed on global Home. -
Background OpenCode status continuity has two safeguards: the SSE read must use a finite timeout so silent half-open sockets enter the service reconnect loop, and
ProcessLifecycleOwner.ON_STARTreconciles every connected OpenCode server from REST snapshots. Snapshot application must remain revision-safe, preserve newer live SSE events, and fail closed when a complete project-scope list cannot be discovered or reused. The sameON_STARTalso merges OpenCode session lists, refreshes Ready DSHsession/listplus theworkspace/followcatalog, and asks an openChatViewModelto merge only that session's history viasession/follow/session/page. A failed Ready DSH catalog refetch reconnects/api/remote.muxinstead of leaving stale running flags. -
Native Chat layout now has focused boundaries:
ChatHeader.ktowns compact/expanded context actions,ChatResponseDock.ktowns retry/permission/question placement above the primary composer,ChatAdaptiveShell.ktownsWindowSizeClassand safe-drawing layout, andChatFollowTailPolicy.ktowns long-session follow-tail state. Keep backend callbacks and the singleLazyListStatescroll owner inChatScreen.kt; do not reintroduce pending request cards in the timeline or hard-coded width breakpoints. -
Chat timeline grammar is owned by
ChatMessageRowPlanner.kt: assistantPart.Reasoning/tool == "skill"/ other tools / leftover file-patch parts become independent Think, Skill, Tool, and Content rows. Assistant prose has no Response bubble chrome; user messages stay bubbles.ChatProcessRows.ktowns Think/Skill disclosure chrome. Spec:docs/specs/chat-timeline-grammar.md. -
SessionWorkspaceOverview.ktandSessionProjectsViewport.ktshare a centered 960dp content cap for the selected server's recent work, view controls, and project queue. PreserveSessionListViewModelas the server-scoped state authority when changing the visual hierarchy. -
DSH presets are session-scoped. Chat and new-session flows use
DshPresetPickerSheet;DshConnectWorkspacemust carry explicit choices through blank-session reuse. Never route DSH preset selection through OpenCode's draft-onlyselectAgentpath. Only ordinary idle sessions may switch, and a receipt from an obsolete connection generation cannot apply. Model display follows sessionmodelSelection.next; the catalog's global default is valid only when the projection explicitly selects that default. Switching models keeps the current reasoning effort only when the new model advertises it; otherwise send that model'sdefaultEffortor omit effort instead of forwarding an unsupported leftover High. -
Codex mobile presentation is split between
CodexTimeline,CodexTimelineViewport, andCodexComposerAttachments. The viewport owns the single lazy-list scroll state and usesChatFollowTailPolicy; physical drags, not programmatic scrolling, suspend tail following. -
Codex plan/diff/token notifications have state separate from turn items in
CodexEventReducer. Their thread/turn maps must not overwrite streamed items; reconnect invalidates notification-only state before resume so new events remain authoritative. Do not display missing token usage as zero. -
Codex attachments send image bytes as image data URLs, never Android-local paths as daemon
localImageinputs. Camera files are restricted to the FileProvidercodex-attachments/cache path. Keep binary attachments out of saved-instance Bundles. Skills with parse errors must not hide valid Skills. -
DSH preset reads observe only the active server generation and publish the roster independently of
session/list. Ready starts separate preset and session load jobs; disconnect cancels both.agentPresets/listhas a 15-second deadline that becomes a retryable failure; caller cancellation still propagates. -
DSH current preset comes from
projections.values.agentPresetor its selected log event, not the creation header. Explicit null means unassigned; missing state is unresolved, never the catalog default. Capture the preset sequence before a selection request so a late receipt cannot replace newer live state. -
Codex project screens reuse the shared session presentation components while keeping their native transport and server-scoped project preferences. Remote directory browsing uses
fs/readDirectory; preserve exact directory strings, including trailing spaces, when starting a thread. -
Codex composer Enter inserts a newline. Keep submission on the explicit send button rather than wiring the text field IME action to submit.
-
Codex and native chat share
ChatHeader,ChatResponseDock, andProcessDisclosureRowpresentation. Codexitem/tool/requestUserInputuses the DSH question-card grammar (instant single-select, explicit submit for batch/custom, secret masking) and the native response dock; answers stay the Codex{ answers: { id: { answers: [...] } } }wire. Preserve native composer spacing, AMOLED treatment, and navigation insets when adding backend-specific controls. Composer model/effort are compact chips, not full-width menus. -
Markdown fenced/indented code is selectable and has a one-tap copy control for the inner code. Mermaid diagrams stay unselected. A successful Mermaid WebView render must stay rendered: late JS/Kotlin timeout or failure cannot replace the diagram with the source fence.
-
Tappable Markdown links and workspace paths:
http(s)opens the system browser; absolute/relative workspace files open an in-app preview. Codex reads viafs/readFile; OpenCode usesreadFileText. DSH has no file-content RPC, so preview fails with copy-path. Never send Linux daemon paths through Android file intents. -
Codex user-message images come from
raw.content:image.urlis a data or HTTP URL, whilelocalImage.pathbelongs to the daemon and must use itsfs/readFileRPC. Never resolve remote image paths through Android files. Image load failures must leave a retryable placeholder in the timeline. -
Different Codex threads need independent navigation entries and ViewModel owners, including notification navigation. Same-destination single-top can retain the parent's constructor-bound thread ID. Open with full
thread/resumehistory; do not require a disk-backed read for a running, unmaterialized child. -
Publish a Codex resume snapshot before optional goal/model metadata loads. Late snapshots cannot regress terminal turns, and unrelated notifications must not erase a local open error or a newer server error.
Use Java 21 for Gradle on this host:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew :app:testDebugUnitTest :app:assembleDebugRun one JVM test class with:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew :app:testDebugUnitTest --tests 'fully.qualified.TestClass'If generated Hilt/Kotlin caches fail, rerun the affected verification after
./gradlew clean before diagnosing a source regression.
-
app/src/main/kotlin/dev/wuxie233/codecarry/data/api/: OpenCode wire APIs and DTOs. -
app/src/main/kotlin/dev/wuxie233/codecarry/data/dsh/: DSH Remote RPC,/api/remote.muxdownlinks, event reduce, chat fold, connect-workspace planner, and remaining host unary surfaces. -
app/src/main/kotlin/dev/wuxie233/codecarry/data/repository/: persisted repositories and shared OpenCode event reduction. -
app/src/main/kotlin/dev/wuxie233/codecarry/domain/: backend-neutral models and transport contracts. -
app/src/main/kotlin/dev/wuxie233/codecarry/service/: foreground connection, notification, reconciliation, and local-runtime services. -
app/src/main/kotlin/dev/wuxie233/codecarry/ui/screens/: screen state, ViewModels, and backend-specific user workflows. -
app/src/main/kotlin/dev/wuxie233/codecarry/ui/components/: reusable Compose presentation components. -
app/src/main/res/: Android resources and localized strings. -
app/src/test/: JVM/Robolectric behavior tests. -
app/src/androidTest/: device and physical-gesture tests. -
docs/research/: evidence-backed design and compatibility research. -
turn/startcommand responses contain an authoritative turn ID. Apply that receipt before releasing the composer for steering; do not wait exclusively forturn/started. Ignore a late start receipt when the reducer already knows that turn, preserving streamed items and terminal status. -
Codex ancestry is projected by
CodexThreadTopology.ktfrom the current server catalog. Keep missing-parent subagents visible, and never treatforkedFromIdas a subagent parent. Chat family catalog hydration is optional and must not block the resume snapshot or overwrite newer live state. -
Codex failures retain thread/turn ownership and
willRetry. Local operation errors stay separate from remote notifications; render failures even before their turn snapshot arrives, and retire transient retries on terminal state. -
ConversationExport.ktcopies the loaded data snapshot independently of lazy rows and disclosure state. Label this as loaded messages; it does not fetch older pages. EachMessageMarkdownContentshares prose selection within its row while code, tables and WebViews keep separate interaction ownership. Table measurement-only text must not expose accessibility nodes; only the placed table cells belong in the semantics tree. -
Codex thread display titles fall back from explicit
nametoagentNickname(legacysource.subAgent.thread_spawn.agent_nickname) before preview. Keep list, chat, related-thread labels and search aligned with that identity. -
Codex pending image attachment bodies open the shared image preview; only the separate remove button deletes an attachment.
-
Codex catalogs page by stable
created_atwhile presentation keeps recency order. Database-only pages publish progressively without deleting omitted threads; archives load on demand. Repeated cursors fail the refresh. Only complete active plus archived repair snapshots from the same connection generation may reconcile omissions. Filesystem repair is delayed and throttled, never part of first-page readiness. Reducer reset generations distinguish connection teardown from thread deletion. List screens retain loaded metadata across resets and rebind on manager entry replacement; socket reconnect refetches the catalog. -
Reconnect
thread/resumemust merge full history with newer live turns and items, preserving snapshot order and appending live-only entries. Do not replace the thread wholesale with a late resume receipt. -
Codex socket readiness and thread subscription readiness are independent. Use manager-owned, generation-fenced
resumeThreadfor shared full-history recovery and gate thread controls withisThreadReady. Recently closed subscriptions have a bounded grace period; a sent unsubscribe must finish before resuming. Cache acknowledgements of model/effort/service tier so a reused subscription cannot restore obsolete composer choices. -
Keep cached chat history visible during recovery. Optional goal/model loads cannot block history or overwrite newer choices. List and family projections exclude historical items, and unrelated thread deltas must not rebuild the current chat. Decode large RPC thread payloads off the UI dispatcher.
-
Codex
imageGenerationitems render inline fromresult(raw base64), withsavedPathread through the daemon when inline bytes are absent. Reuse the timeline image retry and preview UI. A late in-progress resume snapshot must preserve completed imageraw/extra, where its output bytes are stored. -
Codex Fast uses the selected model's advertised
serviceTiers; a pending choice applies only toturn/start, never steering. Explicit JSON null clears a service tier; omission inherits it. Persist only unsent choices per server/thread, then restore the acknowledged tier fromthread/resume. -
Codex account quota lives in
CodexConnectionManager, shared per server, independently of thread token usage. Merge sparse rate-limit pushes without clearing nullable metadata; in-flight reads must preserve newer pushes. Account changes clear cached usage, socket changes fence old reads, and disconnected values remain visibly stale. Window durations and resets come from the server, and missing quota must never render as zero. -
Codex full thread history is a single WebSocket message. The bridge allows 64 MiB daemon responses and 16 MiB client requests; preserve explicit 1009 size-limit diagnostics and Android close codes. APK installation does not update the bridge process; bridge changes require a separate deployment.
-
DSH catalog/control metadata is not a history snapshot. Preserve visible chat messages across reconnect until
historyOpened; a known chat follows as soon as Ready, independently of catalog refresh. Register the mux collector before sendingsession/followopen so fast opening snapshots cannot be dropped. -
Codex archive/unarchive receipts and lifecycle notifications invalidate cached subscription readiness;
thread/closedalso requires recovery for an owned, unarchived thread. Publish readiness changes even when the socket stays open. Unsubscribe has a 10-second cleanup deadline; on timeout invalidate that socket generation before resuming, since a late unsubscribe has an uncertain effect. -
A displayed read anchor may already match the latest reply while a late unread mark remains. Clearing the mark must not require advancing the anchor. Observe persisted unread changes, and avoid clearing from an outdated UI projection.
-
Codex asynchronous questions are
agentMessageitems withdelivery=async, including items whosephase=final_answer. Exclude them from the final-answer presentation. Reply using the original active turn'sturn/steerand thesend_user_message_question_replywrapper; never fall back to starting a turn. -
Codex history merges keep live-only items near shared ordering anchors and preserve fields changed since the read baseline. A cached subscription is not a history refresh: explicit reconnect and returning to chat request fresh history.
-
Codex follow-tail observes measured layout changes as well as message changes. Late Markdown/image measurement must preserve tail following, while physical history dragging and explicit process disclosure navigation keep ownership of the viewport.
Track issues and specs in GitHub Issues for Wuxie233/codecarry.
Before issue operations, read docs/agents/issue-tracker.md.
Use the five canonical triage roles.
Before applying triage labels, read docs/agents/triage-labels.md.
Use a single-context layout.
Before domain exploration, read docs/agents/domain.md.