The mobile plugin Host is a long-lived Node.js process inside the embedded iSH Alpine guest. It does not execute commands on a server. Model inference remains in the native provider client; provider credentials are intentionally excluded from this process.
- Static Host files and pinned npm packages live under
/workspace/.harness-mobile/plugin-host. - Model-defined Cordis Packages live only in
DynamicCordisRunnerServicememory. The session ID establishes upstream Agent ownership, but the native app does not persist Package source as part of its conversation checkpoint. - Stopping or restarting the Host process drops every dynamic definition and active Fiber. Starting the Host again restores only the pinned baseline packages and official Cordis tools.
define,run,stop, andundefinecall the published DeepSeek runner. The bridge does not implement a second lifecycle.- The Host loads the published
@deepseek-ai/dsh-tool-cordisnamespace plugin. The model therefore uses the upstream self-inspection and lifecycle tools rather than mobile reimplementations of those tools. - Every active Package is a real Cordis Fiber, so effects are disposed by upstream Cordis on stop, update, failure, or process exit.
- Host-half Packages may use Tools, Prompt sections/contexts/variables, Cordis services/events, and private
harness.handlemethods inside the Host process. The bundled Host publishes Tool/Prompt contributions plus active handler/service metadata. The Swift DTO/bridge maps only a fixed handler/service allowlist to native Memory, Orchestration, Agent, Sandbox and Tool checkpoints. - The Host mounts the published Session store, Agent registry, Skill registry, and SessionQuery service. Swift incrementally synchronizes the active mobile session log and current Skill catalog through
context/sync; hosted tool execution is wrapped byctx.agents.withInitiator(), soexec.agent.session,ctx.agents.currentInitiator(),ctx.skills,ctx.sessionQuery.readSurface(), and committedsession/eventobservations match the desktop service contracts. - Browser Client halves are rejected by both the RPC adapter and the model-facing Tool guard until a native mobile Client runner is available.
The Host contributes the official DSH tools to the native Agent after a successful native synchronization:
cordis_inspect_listcordis_inspect_querycordis_inspect_selfcordis_definecordis_runcordis_stopcordis_undefine
The official tool:cordis prompt section is preserved. A second mobile policy section states that this deployment has no browser Client runner, does not mount the desktop cordis-plugin-development Skill bundle, keeps definitions only in process memory, and never accepts provider credentials. The native app does synchronize its own current Skill catalog into the official Host Skill registry. This policy prevents the upstream dual-plane tool from entering a Client-pending state that the native app cannot complete. The model uses Inspect as the exact contract for the services actually mounted on the phone.
Lifecycle actions issued through AppModel synchronize inventory and contributions immediately. After a successful model-facing cordis_define, cordis_run, cordis_stop, or cordis_undefine, ISHHostedCordisTool waits for the aggregate native bridge to synchronize before it returns control to the next Agent step. Ordinary hosted tools do not trigger a redundant refresh.
Host-private methods registered with harness.handle and callable methods on plugin-owned Cordis services are dynamically callable through the invoke RPC. Both targets require the session ID, plugin ID and exact active plugin run ID. If the Host advertises a handler or service method with a recognized checkpoint identity, ISHPluginHostDynamicHarnessBridge attaches it to that typed native boundary. Unknown names remain management RPC endpoints; they do not automatically become model tools or enter the native checkpoint chain.
stdin and stdout use newline-delimited JSON-RPC 2.0. stdout is reserved for protocol frames. Cordis and Package console output is redirected to stderr.
Supported methods:
| Method | Purpose |
|---|---|
ping |
Protocol, Host, and pinned package versions |
inventory |
Source-free dynamic Plugin and Package lifecycle state |
define |
Define an immutable in-memory Package version |
run |
Start or update a Host-only Package through DynamicCordisRunnerService |
stop |
Dispose the active Fiber but keep Package definitions |
undefine |
Stop and forget a Plugin and every Package version |
context/sync |
Incrementally append the active mobile Session events and replace the synchronized Skill catalog |
contributions |
Current Tool/Prompt snapshot and active handler/service directory |
invoke |
Execute a dynamic Tool, registered harness.handle method, or plugin-owned service method |
settings/describe |
Secret-redacted official Settings namespace/schema/layer/revision snapshot |
settings/mutate |
Revision-checked path set/delete operations |
settings/update |
Revision-checked official namespace update |
settings/replace |
Revision-checked official namespace replacement |
The JSON-RPC lifecycle methods remain available for native management UI and recovery. They are not alternate implementations of the model-facing Cordis tools; both paths delegate to the same upstream DynamicCordisRunnerService registry.
The bundled dispatcher implements tool, handler, and service invoke targets. contributions exports active handlers and plugin-owned service methods with their plugin and run identities. Handler invocation rejects a missing, stopped, or stale activation. Service invocation additionally requires the exact active Fiber, verifies that the requested service belongs to that Fiber, and accepts only a callable method discovered from the service implementation. Handler and service return values must be representable as lossless JSON before they cross the process boundary.
This callable directory is intentionally broader than automatic Agent integration. Only recognized handler names and service-name/method pairs are adapted to the compiled native checkpoint allowlist. Unrecognized handlers and services remain available to explicit management RPC callers but are not inserted into the model tool catalog or the native Agent loop. Cordis events and all other Host-only effects stay inside the iSH Host process.
The typed iSH bridge currently forwards these waterfall checkpoints:
memory/recall,orchestration/pre-step, andagent/pre-step;orchestration/requestandagent/request;orchestration/request-errorandagent/request-error;- credential-firewalled
llm/streamevent bridge (start, per-event decision, and terminal notification); sandbox/pre-execute,tools/pre-execute,tools/execute, andtools/post-execute.
It also forwards memory/record, orchestration/turn-stopping, and agent/turn-stopping, plus the official read-only lifecycle events agent/created, agent/disposed, agent/status, agent/session-start, agent/error, tools/result, and tools/change. Payloads expose only fields actually owned by the corresponding Swift type. In particular, memory/record has no invented agent or turn coordinate, while turn-stopping payloads include their agent, run, turn, step, and messages.
The inbox lifecycle is also observable through three typed, observe-only events:
agent/inbox/insertedis emitted after the native AppModel successfully inserts a queued user, child-report, or job-completion message for the active run;agent/inbox/claimedis emitted after the MessageId-addressed committer durably removes a pending occurrence for the next safe turn/step;agent/inbox/discardedis emitted after anagent/inbox/pre-claimplugin decision discards that exact MessageId and the committer succeeds.
Each event carries the stable message ID, text, disposition, creation time, source, boundary, agent ID, and run ID. discarded additionally carries a bounded reason. Plugins cannot mutate identity, source, disposition, timestamp, or queue ownership after insertion/claim/discard. Event listeners are fail-isolated: a throwing observer is traced locally and never aborts the native Agent loop. These events are runtime observations, not a persisted conversation checkpoint.
The following upstream surfaces have explicit mobile boundary notes:
llm/streamis exposed through an event-acknowledged Host bridge. Swift keeps the provider client, API key, URLSession, and sourceAsyncThrowingStream; the Host receives aphase: "start"envelope followed by onephase: "event"envelope per chunk. The Host must return{kind: "next"}(or"observe") to release the chunk,{kind: "drop"}to consume it, or{kind: "replace", event: ...}to replace it. Swift awaits each response, so Host processing supplies real backpressure.finish,error, andcancelterminal envelopes are best-effort notifications. Host failures fail open for the native stream, while cancellation still propagates through the SwiftAsyncThrowingStream. No provider credential or authorization header is present in any envelope.tools/code-dispatch-logbelongs to the nativerun_codechild-dispatch producer. The mobile bridge forwards it only for an active Code Mode call; it is never advertised as a standalone Host capability.- The three inbox lifecycle events above are represented by the native Agent inbox and are included in the Host allowlist. The inserted projection intentionally omits a turn coordinate because insertion may occur while the session is idle; claimed/discarded are emitted only after the durable committer succeeds.
The maximum Swift-to-iSH request frame is 512 KiB, below the persistent bridge's 1 MiB pending stdin bound. Responses are framed up to 4 MiB. Swift submits outbound frames through one FIFO writer, with a bounded 2 MiB client queue and bounded backoff when iSH reports stdin backpressure. A rejected frame fails only its RPC after the retry window; it does not tear down an otherwise-running Host. Transport/process failures still invalidate the Host so the next lifecycle refresh can restart it.
The Host mounts the published @deepseek-ai/dsh-settings and @deepseek-ai/dsh-settings-file providers. The user document is /workspace/.harness-mobile/settings.yaml; the file provider watches it and publishes official document revisions, so compatible plugin settings can update without restarting the native Agent.
settings/describe uses the official secret-redacted descriptor and projects the upstream schema into a bounded wire form. Swift preserves the upstream defaults/base/user layers and treats an explicit value equal to the default as an override. Editing is staged locally; Save emits one batch of path mutations with expectedRevision. A revision mismatch returns -32012 with reason: settings-conflict, and the UI keeps the draft so it can be rebased onto the new revision.
Secret schema nodes and credential-shaped keys or values never cross the Swift/iSH wire. Secret fields show only configured/not-configured state and cannot be written through these RPC methods. Transform, lazy, recursive, lossy union, and secret-bearing dynamic collection schemas fail closed; arrays and dictionaries that cannot be edited losslessly remain visible but read-only.
runaccepts upstreamrunandupdatemodes. Cordis owns the active Fiber and disposes its effects during stop, update, failure and process exit.- The published dynamic runner retracts the old Fiber before an
updatestarts the new Package and does not automatically reactivate the old Fiber when startup fails. The old Package definition remains addressable and can be run again, but this is recovery, not an atomic rollback guarantee. - Each synchronized Host revision is represented by the native
ish.dynamic-contributionsplugin. Updating contributions hot-replaces that bridge without restarting the native Agent. - Successful official Cordis lifecycle Tool calls synchronize that revision before the next model step, so newly added or removed Tool/Prompt contributions and handler/service bindings do not require a manual Plugin-screen refresh.
- Native plugin effects are tied to a plugin generation. Stale asynchronous work cannot register into a newer generation, and an activation failure removes every partial Tool, Prompt, service, event and checkpoint effect owned by that generation.
CordisPluginRuntime.replacerestores the previous active native factory when a replacement cannot activate. Dependants disconnect and reconnect through service reconciliation.- If the iSH process, JSON-RPC framing, credential firewall or bridge synchronization fails,
AppModelstops the Host, removes the aggregate bridge and reports the Host as failed. Stale dynamic tools are not left registered in the native Agent. - Service isolation labels apply to the native Cordis runtime. The iSH Host remains one long-lived process and is not a separate OS sandbox per Package.
- The dynamic harness adapter is allowlisted and typed. Optional memory/orchestration and observer failures fall back to the native chain, hosted Tool execution failures become Tool errors, and an unavailable hosted Sandbox policy denies execution.
Native Cordis waterfall execution records checkpointStarted, checkpointFinished, and checkpointFailed trajectory events. Agent-loop calls carry the run ID, turn and step. Each event also records the exact ordered interceptor chain, including native plugin ID, generation, label, scope and prepend order; labels for iSH adapters retain the Host plugin/run endpoint identity.
Structured checkpoint inputs and successful outputs are projected into bounded JSON before entering the trajectory ledger. Strings, arrays, objects and nesting depth are limited, and credential-shaped fields such as apiKey, authorization, accessToken, refreshToken, secretKey, clientSecret, and password are replaced with <redacted>. DeepSeek/OpenAI-style sk-... values and bearer tokens are also removed from free-form strings. HarnessMobileTests/CordisPluginRuntimeTests.swift covers the generation chain, structured checkpoint projection and credential redaction.
These waterfall checkpoints are runtime extension boundaries. They are separate from the persisted conversation checkpoint used for session recovery; dynamic Packages, handlers, services and Fibers are not restored after the Host process exits.
The native transport passes an explicit environment allowlist. It never forwards the app process environment or provider configuration. Swift and JavaScript both reject:
- credential-shaped keys such as
apiKey,authorization,accessToken, orclientSecret; - DeepSeek/OpenAI-style
sk-...values; - bearer authorization values.
This means dynamic tools cannot ask the native layer to pass the model provider key into iSH. A plugin that needs network credentials requires a separate, explicit capability design rather than reusing model credentials.
ISHPluginHostInstaller.installIfNeeded stages the bundled resources, ensures guest networking remains available for the installation lease, and runs install.sh inside iSH. The script installs Alpine Node.js/npm and then runs npm ci with lifecycle scripts disabled.
package.json pins every direct DeepSeek package to one version, including @deepseek-ai/dsh-tool-cordis. package-lock.json locks the full transitive tree and package integrity. manifest.json records the audited top-level versions, integrity values, the primary registry, and optional mirror addresses.
Only this bundled, version-locked npm tree is installed. Model-defined Host-half Package source is evaluated as process-memory code by the published Cordis runner; it is not a persistent marketplace installation and cannot request arbitrary npm packages.
The installer accepts an HTTPS registry and mirror URL. It tries the selected primary registry first and retries the mirror only when the primary download fails. npm cache data is kept in /tmp and removed after installation.
The minimum AppModel ownership is one installer and one client for the shared workspace:
let installation = try await ISHPluginHostInstaller.shared.installIfNeeded(
workspaceURL: workspaceURL,
mirrorURL: URL(string: "https://registry.npmmirror.com")
)
let transport = ISHPersistentPluginHostTransport(workspaceURL: workspaceURL)
let client = ISHPluginHostClient(transport: transport)
try await client.start()
_ = try await client.ping()
let current = try await client.contributions(sessionId: sessionID)AppModel retains ISHPluginHostClient, stops it during explicit teardown, and synchronizes inventory plus contributions after App-owned or official model-facing lifecycle calls. ISHPluginHostCordisBridge converts Tool/Prompt DTOs and allowlisted handler/service metadata into native Cordis contributions, so AgentRuntime does not know about ISHShellProcess or NDJSON framing.
- Select one published DSH release train and exact Cordis version.
- Update exact versions in
package.json. - Regenerate
package-lock.jsonwith npm and review all transitive changes. - Update
manifest.jsonversions and integrity fields from the lockfile. - Increment
hostVersionwhen Host behavior, bundled resources,package.json, orpackage-lock.jsonchanges. IncrementprotocolVersionfor a breaking JSON-RPC shape or semantic change and update the Swift DTO/client in the same change. - Run Swift protocol/runtime tests and the Node Host smoke test before staging on the phone.
- Install on the iPhone and verify first install, mirror fallback, restart loss of dynamic definitions, lifecycle operations, contribution refresh, Settings persistence/watch/revision conflicts, cancellation and Host failure cleanup.
The install stamp includes Host and protocol versions. A changed stamp causes npm ci to reconcile the on-phone dependency tree; dynamic Package definitions are intentionally not migrated because they are process-memory-only.