Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ flowchart LR

## MobileCore Link Status

MobileCode no longer hard-codes a local Qwen model. `MobileCoreClient` resolves the active model, runtime, revision, backend, quantization, capabilities, artifact state, resource preflight, Android background-restriction state, recommendations, and performance metrics from the co-installed MobileCore service. The in-app TuiMa sheet can load, unload, and switch installed models by public `model_id`; ordinary clients never receive or submit absolute model paths. A background-restricted MobileCore remains visible for recovery but is removed from eligible local routes before inference payloads are sent.
MobileCode no longer hard-codes a local Qwen model. `MobileCoreClient` resolves the active model, runtime, revision, backend, quantization, capabilities, artifact state, resource preflight, Android background-restriction state, recommendations, and performance metrics from the co-installed MobileCore service. The in-app TuiMa sheet can load, unload, and switch installed models by public `model_id`; ordinary clients never receive or submit absolute model paths. Cross-model switching projects the memory available after reclaiming the matching active runtime, retains safety headroom, and revalidates the runtime immediately before loading so a stale snapshot cannot trigger a lifecycle request. A background-restricted MobileCore remains visible for recovery but is removed from eligible local routes before inference payloads are sent.

Image and audio buttons appear only when the active local runtime advertises the corresponding capability. Attachment bytes stay in memory, are sent only to `127.0.0.1`, are never persisted in chat turns or evidence, and never fall back to a cloud provider. Local inference evidence records safe model/runtime/latency metadata while omitting prompts, media, credentials, and payloads.

Expand Down
27 changes: 27 additions & 0 deletions docs/mobilecore-dual-app-qa.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ IDs containing path separators or control characters are rejected before a
request is sent, and a switch is successful only when `/health` reports the
exact requested public model ID.

Model switching also performs a projected post-switch memory check. MobileCode
may count the current runtime peak as reclaimable only when health and metrics
identify the same active model, caps that value by the active model's estimated
memory, and keeps 10% projected headroom. Under pressure it reduces requested
context to at most 2048 tokens. The client revalidates the runtime identity
immediately before the load request; a concurrent model, backend, capability,
quantization, or background-state change returns `runtime_snapshot_changed`
without sending `/mobilecore/model/load`.

The TuiMa control sheet exposes that state inside MobileCode. When MobileCore
reports a complete verified Omni pair that is not active, the sheet offers an
explicit local activation control. Image and audio entry points remain hidden
Expand Down Expand Up @@ -125,6 +134,23 @@ ID as active with 456 MB peak memory, before restoring the exact Qwen2.5 public
ID. This proves the v2 control path against the running dual-app service, but it
remains emulator evidence and does not satisfy the physical-device gate.

### v0.1.78 projected-switch and offline regression

On 2026-08-07, the final `pureDebug` v0.1.78 (`68`) candidate containing the
projected-memory switch preflight was clean-built and reinstalled with its
Android test APK. The MobileCode APK SHA-256 was
`f7ef72d6f615edae7015e6113d0c60b14eabf0ae42181ebae7b2b490581f705c`.

The same Android 16 ARM64 emulator then passed the full 30-task cross-app lane
again in airplane mode: 15 buffered requests and 15 SSE requests completed in
274.615 seconds. The test asserted non-empty local output, SSE completion,
MobileCore metrics, and absence of the controlled prompt marker in metrics.
Airplane mode was restored by the host runner. MobileCode cold-launched as
v0.1.78, MobileCore 0.1.4-rc6 remained a foreground service with protocol v2
and the real Qwen2.5 GGUF loaded, and the post-run log scan found no app fatal
exception, ANR, OOM, or SIGABRT. This is repeatable emulator evidence; it does
not replace the pending physical-device lane.

### One-task cloud approval check

On 2026-08-07, the one-task cloud approval path was exercised through the real
Expand Down Expand Up @@ -461,6 +487,7 @@ Raw screenshots and sanitized logcat remain under the ignored `.qa-artifacts/` d
background-recovery, and APK-signing paths pass 12 deterministic host-side
unit tests.
- ActionEvidence inference-to-device linking passes focused unit coverage, including idempotency and action-type rejection.
- The final v0.1.78 client regression passes 582 Flutter tests, including stale-runtime rejection before model load and projected-memory refusal without a load request.

## Remaining Release Gates

Expand Down
3 changes: 3 additions & 0 deletions docs/releases/v0.1.78.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ Release type: local-model linkage and Android background-safety pre-release patc
- Reject local inference with a typed `background_restricted` error before sending prompts or attachments, while showing a user-controlled Android Battery-settings recovery instruction.
- Keep a restricted but loaded model visible in the coherent MobileCore runtime snapshot instead of misclassifying the control plane as inconsistent.
- Activate the pinned Omni runtime for a selected local image or audio task only when both artifacts are verified, then re-check the live capability before sending media bytes.
- Project post-switch memory from the coherent runtime snapshot, count only bounded matching active-runtime memory as reclaimable, retain 10% headroom, and reduce context under pressure.
- Revalidate the active runtime immediately before model load so a concurrent model, backend, capability, quantization, or background-state change fails without sending the lifecycle request.
- Preserve the v0.1.77 public-build credential policy: no provider keys, relay bearer tokens, or OAuth client secrets are compiled into distributed Android/iOS binaries.

## Acceptance gates

- Focused MobileCore provider and adaptive-policy tests cover restricted probing, zero-payload inference rejection, coherent restricted snapshots, and verified Omni activation.
- The full Flutter test suite and targeted analyzer complete without fatal diagnostics.
- The final client regression passes 582 Flutter tests; the Android 16 ARM64 emulator passes 30/30 offline cross-app tasks with the rebuilt v0.1.78 APK.
- The exact upload-signed `0.1.78+68` APK passes version, certificate, credential-pattern, install, cold-launch, and fatal-log checks.
- With MobileCode foregrounded, MobileCore `0.1.4-rc6` retains a real local model for 40 authenticated health polls and one controlled inference without FGS timeout, freeze, ANR, OOM, or SIGABRT.

Expand Down
27 changes: 5 additions & 22 deletions mobile_agent/lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14125,32 +14125,15 @@ class _ChatPanelState extends State<_ChatPanel> {
Future<void> _switchMobileCoreModel(String modelId) async {
final startedAt = DateTime.now();
try {
final recommendation = await _tuimaProviderService.recommendations();
final matches = recommendation.recommendations
.where((item) => item.modelId == modelId)
.toList(growable: false);
final entry = matches.isEmpty ? null : matches.first;
final constrained = entry?.fit == 'too_tight';
if (constrained) {
throw const MobileCoreProviderException(
code: 'insufficient_memory',
message:
'MobileCore reports that this model is too tight for the device.',
);
}
final contextLength = entry == null || entry.contextLength <= 0
? 4096
: entry.contextLength.clamp(512, 8192);
await _tuimaProviderService.switchModel(
modelId,
contextLength: contextLength,
);
await _refreshTuimaHealth();
final result =
await _tuimaProviderService.switchModelWithPreflight(modelId);
if (mounted) setState(() => _tuimaHealth = result.health);
await _recordMobileCoreControl(
operation: 'switch_model',
modelId: modelId,
startedAt: startedAt,
success: true,
safeMetadata: {'switchPreflight': result.plan.evidenceMetadata},
);
} on Object catch (error) {
await _recordMobileCoreControl(
Expand Down Expand Up @@ -14389,7 +14372,7 @@ class _ChatPanelState extends State<_ChatPanel> {
title: Text(model.id,
style: const TextStyle(color: _text)),
subtitle: Text(
'${model.quantization} · ${_formatBytes(model.sizeBytes)} · context ${model.contextLength}'
'${model.backend} · ${model.quantization} · ${_formatBytes(model.sizeBytes)} · context ${model.contextLength}'
'${model.capabilities.imageInput ? ' · image' : ''}',
style: const TextStyle(color: _muted, fontSize: 12),
),
Expand Down
Loading