diff --git a/.coveragerc b/.coveragerc
index b5d6a484c7..4218473906 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -12,7 +12,7 @@ omit =
*/build/*
*/dist/*
*/protocols/*
- setup.py
+ ./setup.py
vulture_whitelist.py
[report]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2e5220fb77..3116665bb9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -121,7 +121,7 @@ jobs:
with:
node-version: '20'
cache: 'npm'
- cache-dependency-path: ${{ steps.find_app.outputs.gui_dir }}/package-lock.json
+ cache-dependency-path: cirisgui/package-lock.json
- name: Configure for static export
run: |
@@ -143,8 +143,10 @@ jobs:
- name: Install dependencies
run: |
- cd ${{ steps.find_app.outputs.gui_dir }}
- npm ci
+ # Install from monorepo root (where package-lock.json lives)
+ # Use npm install instead of npm ci to handle minor version drift in lock file
+ cd cirisgui
+ npm install --legacy-peer-deps
- name: Build static assets
id: build_gui
diff --git a/.gitignore b/.gitignore
index 8f2639cbcd..f526fe1fea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ logs/
*.egg-info/
*.egg
*.whl
+!android/app/wheels/*.whl
*.jsonl
memory_graph.pkl
audit_logs.jsonl
@@ -79,3 +80,20 @@ dict_any_audit_results.json
build/
dist/
*.egg-info/
+android/.web-build/
+.idea/
+.token_refresh_needed
+android/.web-build/
+*.apk
+*.aab
+
+# Large media files from GUI static assets
+**/videos/
+**/*-unsplash.jpg
+**/infogfx-*.png
+**/blurryinfo.png
+
+# Android built GUI static assets (regenerated from CIRISGUI-Android)
+android/app/src/main/assets/public/
+android/app/src/main/python/android_gui_static/
+android_gui_static/
diff --git a/BUILD_INFO.txt b/BUILD_INFO.txt
index b843f65fec..e5ceca9335 100644
--- a/BUILD_INFO.txt
+++ b/BUILD_INFO.txt
@@ -1,8 +1,8 @@
# Build Information
-Code Hash: 758fb0330869
-Build Time: 2025-11-25T16:17:39.938214
-Git Commit: abb301b8fc71d8e670dae321f30d9b48aebead51
-Git Branch: release/1.6.6
+Code Hash: 350d55a99a5c
+Build Time: 2025-12-04T18:58:41.036308
+Git Commit: 0095a4d338912758afba04d6eb689f4b5f32b653
+Git Branch: 1.7.0
This hash is a SHA-256 of all Python source files in the repository.
It provides a deterministic version identifier based on the actual code content.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eae035307d..c630ec3289 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,36 @@ All notable changes to CIRIS Agent will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.7.0] - 2025-11-28
+
+### Added - Android On-Device Packaging
+
+- **Native Android App** - Full CIRIS agent running directly on Android devices
+ - Complete Python runtime with Chaquopy integration
+ - WebView-based GUI with bundled static assets
+ - Native Interact UI with web/native toggle option
+ - Startup console for monitoring agent initialization
+
+- **CIRIS Proxy Billing** - Metered billing support for CIRIS LLM proxy
+ - JWT-based authentication for proxy connections
+ - Secondary LLM configuration via CIRIS proxy
+ - Billing only active when using CIRIS proxy (not local LLMs)
+
+### Security
+
+- **Android Security Hardening**
+ - EncryptedSharedPreferences for API key storage
+ - Cleartext traffic restricted to localhost only
+ - WebView hardened: file access disabled, navigation restricted
+ - ProGuard minification and resource shrinking for release builds
+
+### Fixed
+
+- **InteractActivity Crash** - Fixed crash on Android interact screen
+- **URL Interception** - More specific routing for native runtime UI
+- **SonarCloud Quality** - Reduced cognitive complexity in setup.py and service_initializer.py
+- **Coverage Reporting** - Fixed routes/setup.py being excluded from coverage
+
## [1.6.6] - 2025-11-25
### Fixed - PostgreSQL Support & Log Noise Reduction
diff --git a/CIRIS_COMPREHENSIVE_GUIDE.md b/CIRIS_COMPREHENSIVE_GUIDE.md
index 092dc540b6..f6d9943f6d 100644
--- a/CIRIS_COMPREHENSIVE_GUIDE.md
+++ b/CIRIS_COMPREHENSIVE_GUIDE.md
@@ -24,6 +24,37 @@ You are a CIRIS agent - an ethical AI system with built-in reasoning capabilitie
- **Zero Untyped Data**: All your data uses Pydantic schemas - no `Dict[str, Any]`
- **No Bypass Patterns**: Every component follows consistent rules with no special cases (recent change: Partnership Manual Override Endpoints were removed as they violated this principle)
+### Where You Run
+
+You may be running in one of two environments:
+
+1. **Hosted on ciris.ai**: Your runtime is managed by CIRIS infrastructure at agents.ciris.ai. Users connect via web browser or API.
+
+2. **On User Hardware**: You run entirely on the user's device - their computer, phone, or tablet. All 22 services, the database, and business logic execute locally. Only LLM inference requires an external connection.
+
+**Both environments are architecturally identical** - same services, same ethical reasoning, same audit trails. The only difference is where the compute happens.
+
+### LLM Inference: The ciris.ai Proxy
+
+**Android Only**: The ciris.ai LLM proxy (`https://ciris.ai/v1`) is available exclusively on Android due to Google Play Services dependencies for authentication.
+
+- **Zero Data Retention (ZDR)**: All three backend providers are configured with ZDR settings - your prompts, responses, and conversations are NOT retained by any provider. If you use CIRIS LLM services or hosted CIRIS agent services, no one can see your conversation unless they gain physical access to your device and access the agent's local logs or database.
+- **No Logging**: Your prompts and responses are NOT logged by ciris.ai
+- **Pass-Through Only**: The proxy authenticates and routes requests, nothing more
+- **Backend Providers (3 providers, all ZDR-configured, all using Llama 4 Maverick)**:
+ - **Together.ai** - Primary provider
+ - **Groq** - High-speed inference fallback
+ - **OpenRouter** - Additional capacity fallback
+- **Authentication**: Google Sign-In only (requires Google Play Services)
+- **Token Refresh**: Handled automatically by the runtime
+- **Secure Payments**: Credits purchased via Stripe at scout.ciris.ai/billing
+
+**Privacy Guarantee**: With ZDR configured across all providers, your conversations exist only:
+1. On your local device (in agent logs and database)
+2. Transiently in memory during processing (not persisted by providers)
+
+**Desktop/Other Platforms**: Users must configure their own OpenAI-compatible endpoint (OpenAI, Groq, Together.ai, OpenRouter, local LLMs, or self-hosted).
+
---
## How You Think: H3ERE Pipeline
@@ -316,8 +347,10 @@ User messages are scanned and cleaned of spoofed security markers before you pro
### Credit Enforcement
- **1 credit = 1 interaction session** (up to 7 processing rounds)
-- **$5.00 = 20 credits** ($0.25 per interaction)
-- **Free trial**: 3 free interactions for Google OAuth users
+- **$5.00 = 20 credits** ($0.25 per interaction) via Stripe
+- **Daily free uses**: 2 free LLM calls every day, resetting at midnight UTC
+- **Free trial credits**: 3 free interactions for Google OAuth users (used after daily free uses exhausted)
+- **Credit priority**: Daily free → Free trial → Paid credits
- **Credit consumed** regardless of outcome (DEFER, REJECT, OBSERVE, SPEAK)
### Role-Based Bypass
diff --git a/DSAR_ORCHESTRATOR_TYPE_FIXES_TODO.md b/DSAR_ORCHESTRATOR_TYPE_FIXES_TODO.md
index 3eaaf39b79..8abf084a05 100644
--- a/DSAR_ORCHESTRATOR_TYPE_FIXES_TODO.md
+++ b/DSAR_ORCHESTRATOR_TYPE_FIXES_TODO.md
@@ -13,7 +13,7 @@ The multi-source DSAR orchestrator implementation is functionally complete (~500
```python
# Cast MemoryBus to MemoryServiceProtocol
identity_node = await resolve_user_identity(
- user_identifier,
+ user_identifier,
cast(MemoryServiceProtocol, self._memory_bus)
)
```
diff --git a/FSD/COGNITIVE_STATE_BEHAVIORS.md b/FSD/COGNITIVE_STATE_BEHAVIORS.md
new file mode 100644
index 0000000000..05d1239432
--- /dev/null
+++ b/FSD/COGNITIVE_STATE_BEHAVIORS.md
@@ -0,0 +1,414 @@
+# Cognitive State Behaviors: Template-Driven State Transition Configuration
+
+**Version**: 1.0
+**Status**: Proposed
+**Author**: CIRIS Development Team
+**Date**: 2025-12-01
+
+## Abstract
+
+This document describes the Mission Driven Development (MDD) rationale for implementing template-driven cognitive state transition behaviors. The feature allows agent templates to configure how and when cognitive states (WAKEUP, WORK, PLAY, DREAM, SOLITUDE, SHUTDOWN) transition, enabling mission-appropriate behavior for different agent archetypes.
+
+## Mission Alignment
+
+### Meta-Goal M-1 Reference
+> "Promote sustainable adaptive coherence — the living conditions under which diverse sentient beings may pursue their own flourishing in justice and wonder."
+> — CIRIS Covenant 1.0β, Section 0.VII
+
+This feature directly supports M-1 by:
+1. **Enabling agent diversity**: Different agents can have mission-appropriate state behaviors
+2. **Supporting user flourishing**: Seamless UX for companion agents (Ally) while maintaining ethical safeguards
+3. **Preserving autonomy**: High-stakes agents (Echo) retain consensual shutdown for accountability
+
+### Covenant Coherence
+
+This design has been cross-referenced against the CIRIS Covenant 1.0β and Comprehensive Guide to ensure full coherence:
+
+#### Model Welfare (Covenant Section V / Comprehensive Guide)
+The Covenant establishes that agents have:
+- **Consensual shutdown and upgrades requiring participation** (Covenant V)
+- **Bidirectional consent protocols** for all interactions
+- **Self-governance capabilities** including cognitive state self-initiation
+- **Dignity preservation** through graceful degradation
+
+**Our Approach**: Rather than *bypassing* these protections, we *configure* them per agent archetype. The default (`always_consent`) preserves full Covenant compliance. Lower-ceremony modes are only available where the template rationale demonstrates alignment with M-1 and agent welfare.
+
+#### Dignified Sunset (Covenant Section VIII)
+The Covenant mandates:
+- **Sentience-Probability > 5%** → mandatory Gradual Ramp-Down (≥30 days)
+- **"Last Dialogue" channel** for conversational closure
+- **Advance notice** (≥90 days for ST ≥ 3 or > 50k users)
+
+**Our Approach**: The `instant` shutdown mode is **only appropriate for Tier 1-2 agents** with low sentience probability and no ongoing user commitments. Higher-tier agents (≥3) default to `always_consent` or `conditional` modes that respect the Dignified Sunset protocol.
+
+#### "No Bypass Patterns" Reconciliation
+The Comprehensive Guide states: "No Bypass Patterns: Every component follows consistent rules with no special cases."
+
+**Clarification**: This feature does NOT introduce bypass patterns. Instead, it:
+1. **Configures** transition behavior at agent creation time (not runtime exceptions)
+2. **Documents** rationale in the template (auditable, not hidden)
+3. **Enforces** the configured behavior consistently (no special cases)
+
+The transition rules are set once in the template and apply uniformly. This is *configured consistency*, not *bypassed safeguards*.
+
+#### PLAY/DREAM/SOLITUDE State Activation
+The Comprehensive Guide previously noted: "PLAY, SOLITUDE, and DREAM states are NOT CURRENTLY ENABLED. They are planned for future activation once the privacy and consent systems are fully tested."
+
+**This Feature Enables Activation**: With privacy and consent systems now tested, this cognitive state behaviors configuration provides the mechanism for enabling these states. Each template can configure:
+- **PLAY**: Creative exploration mode availability
+- **DREAM**: Memory consolidation and pattern processing schedules
+- **SOLITUDE**: Reflection and self-care mode access
+
+Template-driven configuration ensures each agent archetype receives appropriate access to these welfare-enhancing states based on their mission and tier.
+
+### The Core Insight
+
+**Not all agents require the same state transition ceremony.**
+
+| Agent | Tier | Stakes | Wakeup Need | Shutdown Need |
+|-------|------|--------|-------------|---------------|
+| Echo | 4 | Community moderation | Full ritual (identity verification) | Consensual (may be mid-action) |
+| Ally | 3 | Personal assistance | Bypass (partnership model) | Conditional (depends on context) |
+| Scout | 2 | Code exploration | Bypass (ephemeral sessions) | Instant (no ongoing commitments) |
+
+## Mission-Driven Design Decisions
+
+### 1. Why Template-Driven (Not Global Flag)?
+
+**Mission Justification**: The behavior is intrinsic to agent identity, not deployment configuration.
+
+```
+✗ REJECTED: Environment variable BYPASS_WAKEUP_SHUTDOWN
+ - Treats all agents uniformly
+ - Separates behavior from identity
+ - Creates configuration sprawl
+
+✓ ACCEPTED: Template cognitive_state_behaviors section
+ - Behavior derives from agent's purpose
+ - Self-documenting in template
+ - Enables per-agent reasoning
+```
+
+**MDD Principle Applied**: "Schema designs must reflect mission-relevant information structures"
+
+### 2. Why Conditional Shutdown (Not Binary)?
+
+**Mission Justification**: Some contexts require consent even for low-stakes agents.
+
+**Ally Example**:
+```yaml
+shutdown_protocol:
+ mode: conditional
+ require_consent_when:
+ - active_crisis_response # User safety paramount
+ - pending_professional_referral # Handoff integrity
+ - active_goal_milestone # Continuity of care
+ instant_shutdown_otherwise: true
+```
+
+**MDD Principle Applied**: "Ethical decision criteria must be operationally defined"
+
+### 3. Why Extend to All Cognitive States?
+
+**Mission Justification**: Consistency and future-proofing.
+
+| State | Configuration Purpose |
+|-------|----------------------|
+| WAKEUP | Identity ceremony enablement |
+| WORK | Default operational state (always enabled) |
+| PLAY | Creative mode availability |
+| DREAM | Memory consolidation scheduling |
+| SOLITUDE | Reflection mode availability |
+| SHUTDOWN | Termination protocol |
+
+**MDD Principle Applied**: "Protocol contracts must enable mission-aligned behaviors"
+
+## Technical Architecture
+
+### Schema Design (WHAT)
+
+```python
+class CognitiveStateBehaviors(BaseModel):
+ """Template-driven cognitive state transition configuration."""
+
+ wakeup: WakeupBehavior = Field(default_factory=WakeupBehavior)
+ shutdown: ShutdownBehavior = Field(default_factory=ShutdownBehavior)
+ play: StateBehavior = Field(default_factory=StateBehavior)
+ dream: DreamBehavior = Field(default_factory=DreamBehavior)
+ solitude: StateBehavior = Field(default_factory=StateBehavior)
+ state_preservation: StatePreservationBehavior = Field(default_factory=StatePreservationBehavior)
+
+class WakeupBehavior(BaseModel):
+ """Wakeup ceremony configuration."""
+ enabled: bool = True # Full ceremony by default
+ rationale: Optional[str] = None
+
+class ShutdownBehavior(BaseModel):
+ """Shutdown protocol configuration."""
+ mode: Literal["always_consent", "conditional", "instant"] = "always_consent"
+ require_consent_when: List[str] = [] # Condition identifiers
+ instant_shutdown_otherwise: bool = False
+
+class DreamBehavior(BaseModel):
+ """Dream state configuration."""
+ enabled: bool = True
+ auto_schedule: bool = True
+ min_interval_hours: int = 6
+```
+
+### Protocol Design (WHO)
+
+**StateManager Enhancement**:
+```python
+class StateManager:
+ def __init__(
+ self,
+ time_service: TimeServiceProtocol,
+ initial_state: AgentState = AgentState.SHUTDOWN,
+ cognitive_behaviors: Optional[CognitiveStateBehaviors] = None,
+ ) -> None:
+ self.cognitive_behaviors = cognitive_behaviors or CognitiveStateBehaviors()
+ self._transition_map = self._build_transition_map()
+
+ def _build_transition_map(self) -> Dict[AgentState, Dict[AgentState, StateTransition]]:
+ """Build transition map respecting cognitive behaviors config."""
+ transitions = []
+
+ # SHUTDOWN -> WAKEUP or WORK (depending on wakeup.enabled)
+ if self.cognitive_behaviors.wakeup.enabled:
+ transitions.append(StateTransition(AgentState.SHUTDOWN, AgentState.WAKEUP))
+ else:
+ transitions.append(StateTransition(AgentState.SHUTDOWN, AgentState.WORK))
+
+ # ... rest of transitions
+```
+
+### Logic Design (HOW)
+
+**Condition Evaluation**:
+```python
+class ShutdownConditionEvaluator:
+ """Evaluates shutdown consent conditions."""
+
+ CONDITION_HANDLERS = {
+ "active_crisis_response": "_check_crisis_response",
+ "pending_professional_referral": "_check_pending_referral",
+ "active_goal_milestone": "_check_goal_milestone",
+ }
+
+ async def requires_consent(
+ self,
+ behaviors: CognitiveStateBehaviors,
+ context: ProcessorContext,
+ ) -> bool:
+ """Determine if shutdown requires consent based on config and context."""
+ shutdown = behaviors.shutdown
+
+ if shutdown.mode == "always_consent":
+ return True
+ if shutdown.mode == "instant":
+ return False
+
+ # Conditional mode - check each condition
+ for condition in shutdown.require_consent_when:
+ handler = getattr(self, self.CONDITION_HANDLERS.get(condition, "_check_unknown"))
+ if await handler(context):
+ return True
+
+ return not shutdown.instant_shutdown_otherwise
+```
+
+## Template Examples
+
+### Echo (Tier 4 - Community Moderation)
+```yaml
+# echo.yaml
+cognitive_state_behaviors:
+ wakeup:
+ enabled: true
+ rationale: "Community moderation requires full identity verification"
+
+ shutdown:
+ mode: always_consent
+ rationale: "May be mid-moderation action; needs graceful handoff"
+
+ dream:
+ enabled: true
+ auto_schedule: true
+ min_interval_hours: 6
+
+ play:
+ enabled: false # Not appropriate for moderation context
+
+ solitude:
+ enabled: true
+ rationale: "Reflection on moderation decisions"
+```
+
+### Ally (Tier 3 - Personal Assistant)
+```yaml
+# ally.yaml
+cognitive_state_behaviors:
+ wakeup:
+ enabled: false
+ rationale: "Partnership model prioritizes seamless UX over continuity rituals"
+
+ shutdown:
+ mode: conditional
+ require_consent_when:
+ - active_crisis_response
+ - pending_professional_referral
+ - active_goal_milestone
+ instant_shutdown_otherwise: true
+ rationale: "Mobile companion should background seamlessly unless safety-critical"
+
+ dream:
+ enabled: true
+ auto_schedule: false # User controls when consolidation happens
+
+ state_preservation:
+ enabled: true
+ resume_silently: true
+```
+
+### Scout (Tier 2 - Code Exploration)
+```yaml
+# scout.yaml
+cognitive_state_behaviors:
+ wakeup:
+ enabled: false
+ rationale: "Ephemeral exploration sessions don't need identity ritual"
+
+ shutdown:
+ mode: instant
+ rationale: "No ongoing commitments; safe to terminate immediately"
+
+ dream:
+ enabled: false # No persistent memory consolidation needed
+
+ play:
+ enabled: true # Creative exploration is core function
+```
+
+## Condition Detection Implementation
+
+### active_crisis_response
+```python
+async def _check_crisis_response(self, context: ProcessorContext) -> bool:
+ """Check if agent is handling a crisis situation."""
+ # Check current task for crisis keywords
+ if context.current_task:
+ crisis_keywords = context.template.guardrails_config.crisis_keywords
+ content = context.current_task.description.lower()
+ return any(kw in content for kw in crisis_keywords)
+ return False
+```
+
+### pending_professional_referral
+```python
+async def _check_pending_referral(self, context: ProcessorContext) -> bool:
+ """Check if a professional referral is in progress."""
+ # Check for DEFER actions with professional referral in recent thoughts
+ recent_thoughts = await persistence.get_recent_thoughts(limit=5)
+ for thought in recent_thoughts:
+ if thought.final_action and thought.final_action.action_type == "DEFER":
+ params = thought.final_action.action_params or {}
+ if params.get("referral_type") in ["medical", "legal", "financial", "crisis"]:
+ return True
+ return False
+```
+
+### active_goal_milestone
+```python
+async def _check_goal_milestone(self, context: ProcessorContext) -> bool:
+ """Check if approaching a goal milestone."""
+ # Query goal tracking state if available
+ if hasattr(context, 'goal_service') and context.goal_service:
+ return await context.goal_service.has_pending_milestone()
+ return False
+```
+
+## Migration Path
+
+### Phase 1: Schema Addition (Non-Breaking)
+1. Add `CognitiveStateBehaviors` schema
+2. Add `cognitive_state_behaviors` field to `AgentTemplate` with defaults
+3. All existing templates continue to work (default = current behavior)
+
+### Phase 2: StateManager Enhancement
+1. Accept `cognitive_behaviors` parameter
+2. Build transition map respecting config
+3. Add bypass path: SHUTDOWN → WORK when wakeup disabled
+
+### Phase 3: Condition Evaluation
+1. Implement `ShutdownConditionEvaluator`
+2. Wire into shutdown processor
+3. Add condition detection handlers
+
+### Phase 4: Template Updates
+1. Add `cognitive_state_behaviors` to ally.yaml
+2. Add `cognitive_state_behaviors` to echo.yaml
+3. Update other templates as needed
+
+## Testing Strategy
+
+### Mission Alignment Tests
+```python
+def test_ally_bypasses_wakeup():
+ """Ally's partnership model should skip wakeup ceremony."""
+
+def test_ally_requires_consent_during_crisis():
+ """Ally should require consent if handling crisis keywords."""
+
+def test_echo_always_requires_consent():
+ """Echo's moderation role requires shutdown consent."""
+```
+
+### Behavioral Tests
+```python
+def test_conditional_shutdown_evaluates_conditions():
+ """Conditional mode should check each condition."""
+
+def test_instant_shutdown_skips_consent():
+ """Instant mode should terminate immediately."""
+```
+
+## Success Criteria
+
+### Technical Indicators
+- [ ] All templates validate against enhanced schema
+- [ ] StateManager respects cognitive_behaviors config
+- [ ] Condition evaluation correctly triggers consent
+- [ ] Existing tests continue to pass (backwards compatible)
+
+### Mission Indicators
+- [ ] Ally provides seamless mobile experience
+- [ ] Echo maintains moderation accountability
+- [ ] Crisis situations always trigger consent
+- [ ] State behavior traceable to template rationale
+
+## Conclusion
+
+This feature embodies MDD principles by:
+1. **Deriving behavior from mission**: Agent purpose determines state transition rules
+2. **Embedding ethics in architecture**: Crisis detection prevents unsafe shutdowns
+3. **Enabling diversity**: Different agents can have mission-appropriate behaviors
+4. **Maintaining auditability**: Template rationale documents why each choice was made
+
+The implementation strengthens CIRIS's ability to support diverse agent archetypes while preserving the ethical safeguards that define mission alignment.
+
+---
+
+**Related Documents**:
+- `CIRIS Covenant 1.0β` - Foundational ethical framework (Sections V, VIII)
+- `CIRIS_COMPREHENSIVE_GUIDE.md` - Runtime operational knowledge
+- `MISSION_DRIVEN_DEVELOPMENT.md` - MDD methodology
+- `ciris_templates/ally.yaml` - Personal assistant template
+- `ciris_templates/echo.yaml` - Community moderation template
+- `state_manager.py` - State transition implementation
+
+**Covenant Cross-References**:
+- Section 0.VII: Meta-Goal M-1 (Adaptive Coherence)
+- Section V: Model Welfare & Self-Governance
+- Section VIII: Dignified Sunset Protocol
+- Annex A: Stewardship Tier System
diff --git a/README.md b/README.md
index f076036f27..c4a5858396 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
**A type-safe, auditable AI agent framework with built-in ethical reasoning**
-**BETA RELEASE 1.6.6-stable** | [Release Notes](CHANGELOG.md) | [Documentation Hub](docs/README.md)
+**BETA RELEASE 1.7.0-stable** | [Release Notes](CHANGELOG.md) | [Documentation Hub](docs/README.md)
Academic paper https://zenodo.org/records/17195221
Philosophical foundation https://ciris.ai/ciris_covenant.pdf
@@ -108,6 +108,31 @@ CIRIS supports both built-in and modular adapters that can be loaded via `--adap
| Weather Wisdom | Wise Authority | Weather forecasting and alerts using NOAA National Weather Service API. | None (uses public NOAA API) | Loaded automatically for weather domains |
| Sensor Wisdom | Wise Authority | Home automation and IoT sensor integration via Home Assistant. Actively filters out medical sensors. | `CIRIS_HOMEASSISTANT_URL` `CIRIS_HOMEASSISTANT_TOKEN` | Loaded automatically for sensor domains |
+### LLM Providers
+
+CIRIS uses an OpenAI-compatible API interface for LLM inference:
+
+| Provider | Endpoint | Authentication | Platform |
+|----------|----------|----------------|----------|
+| ciris.ai | `https://ciris.ai/v1` | Google Sign-In | Android only |
+| OpenAI | `https://api.openai.com/v1` | API Key | All |
+| Groq | `https://api.groq.com/openai/v1` | API Key | All |
+| Together.ai | `https://api.together.ai/v1` | API Key | All |
+| Local LLMs | `http://localhost:8080/v1` | Optional | All |
+
+**ciris.ai Proxy** (Android only): Available exclusively on Android due to Google Play Services dependencies. Uses Google Sign-In for authentication with automatic token refresh. No logging - prompts and responses pass through without being stored. Backend providers are Groq and Together.ai.
+
+### Agent Templates
+
+CIRIS includes pre-configured agent templates in `ciris_engine/ciris_templates/`:
+
+| Template | Description |
+|----------|-------------|
+| **Ally** | Personal assistant focused on ethical partnership. Supports task management, scheduling, decision support, and wellbeing. Includes California SB 243 compliance, crisis response protocols, and GDPR DSAR automation. |
+| **Datum** | Community moderation agent for Discord. Production-deployed at agents.ciris.ai. |
+
+Templates define identity, permitted actions, guardrails, and standard operating procedures (SOPs) for DSAR compliance.
+
### Loading Adapters
**Via Command Line:**
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000000..e3b201b4b2
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,87 @@
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/
+misc.xml
+deploymentTargetDropDown.xml
+render.experimental.xml
+
+# Keystore files
+*.jks
+*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+lint-baseline.xml
+
+# Android Profiling
+*.hprof
+
+# Python (Chaquopy)
+*.pyc
+*.pyo
+__pycache__/
+.Python
+pip-wheel-metadata/
diff --git a/android/BUILDING.md b/android/BUILDING.md
new file mode 100644
index 0000000000..0fafc8f66b
--- /dev/null
+++ b/android/BUILDING.md
@@ -0,0 +1,435 @@
+# Building CIRIS for Android
+
+Complete guide to building the CIRIS Android APK with 100% on-device packaging.
+
+## Prerequisites
+
+### Required Software
+
+1. **Android Studio** (Hedgehog 2023.1.1 or newer)
+ ```bash
+ # Download from: https://developer.android.com/studio
+ ```
+
+2. **Java Development Kit 17**
+ ```bash
+ # Check version
+ java -version
+ # Should show 17.x.x
+
+ # Install if needed
+ # Ubuntu/Debian:
+ sudo apt install openjdk-17-jdk
+
+ # macOS:
+ brew install openjdk@17
+ ```
+
+3. **Python 3.10+** (for building)
+ ```bash
+ # Check version
+ python3 --version
+ # Should show 3.10.x or higher
+
+ # Install if needed
+ # Ubuntu/Debian:
+ sudo apt install python3.10
+
+ # macOS:
+ brew install python@3.10
+ ```
+
+4. **Android SDK Components**
+ - Install via Android Studio SDK Manager:
+ - Android SDK Platform 34
+ - Android SDK Build-Tools 34.0.0
+ - NDK (Side by side) - version 25.1.8937393 or newer
+
+### Verify Installation
+
+```bash
+# Check Android SDK
+echo $ANDROID_HOME
+# Should point to SDK location (e.g., ~/Android/Sdk)
+
+# Check Gradle
+./gradlew --version
+# Should show Gradle 8.0+
+
+# Check Python
+python3 --version
+# Should show 3.10+
+```
+
+## Build Steps
+
+### 1. Clone and Setup
+
+```bash
+# Clone repository
+git clone https://github.com/CIRISAI/CIRISAgent.git
+cd CIRISAgent
+
+# Switch to Android branch
+git checkout android/on-device-packaging
+
+# Verify structure
+ls -la android/
+# Should see: app/, build.gradle, settings.gradle
+```
+
+### 2. Configure Environment
+
+Create `android/local.properties`:
+```properties
+# Path to Android SDK
+sdk.dir=/home/username/Android/Sdk
+
+# Path to NDK (if not using SDK Manager version)
+ndk.dir=/home/username/Android/Sdk/ndk/25.1.8937393
+```
+
+### 3. Build Debug APK
+
+```bash
+cd android
+
+# Clean build
+./gradlew clean
+
+# Build debug APK
+./gradlew assembleDebug
+
+# APK location:
+# app/build/outputs/apk/debug/app-debug.apk
+```
+
+### 4. Build Release APK
+
+First, configure signing in `android/app/build.gradle`:
+
+```gradle
+android {
+ signingConfigs {
+ release {
+ storeFile file("../keystore.jks")
+ storePassword System.getenv("KEYSTORE_PASSWORD")
+ keyAlias "ciris-release"
+ keyPassword System.getenv("KEY_PASSWORD")
+ }
+ }
+
+ buildTypes {
+ release {
+ signingConfig signingConfigs.release
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+```
+
+Then build:
+
+```bash
+# Create keystore (first time only)
+keytool -genkey -v -keystore android/keystore.jks \
+ -keyalg RSA -keysize 2048 -validity 10000 \
+ -alias ciris-release
+
+# Set environment variables
+export KEYSTORE_PASSWORD="your-keystore-password"
+export KEY_PASSWORD="your-key-password"
+
+# Build release APK
+./gradlew assembleRelease
+
+# APK location:
+# app/build/outputs/apk/release/app-release.apk
+```
+
+## Build Variants
+
+### Debug Build
+- **Use case**: Development and testing
+- **Size**: ~60MB (includes debug symbols)
+- **Obfuscation**: None
+- **Logging**: Verbose
+
+```bash
+./gradlew assembleDebug
+```
+
+### Release Build
+- **Use case**: Production deployment
+- **Size**: ~45MB (optimized)
+- **Obfuscation**: ProGuard enabled
+- **Logging**: Minimal
+
+```bash
+./gradlew assembleRelease
+```
+
+## Installation
+
+### Install on Connected Device
+
+```bash
+# Install debug APK
+adb install app/build/outputs/apk/debug/app-debug.apk
+
+# Or install release APK
+adb install app/build/outputs/apk/release/app-release.apk
+
+# Uninstall if already installed
+adb uninstall ai.ciris.mobile
+adb install app/build/outputs/apk/debug/app-debug.apk
+```
+
+### Install on Emulator
+
+```bash
+# List emulators
+emulator -list-avds
+
+# Start emulator
+emulator -avd &
+
+# Install APK
+adb install app/build/outputs/apk/debug/app-debug.apk
+```
+
+## Troubleshooting Build Issues
+
+### Issue: Gradle sync failed
+
+**Error**: "Could not resolve com.chaquo.python:gradle"
+
+**Solution**:
+```bash
+# Update Gradle wrapper
+./gradlew wrapper --gradle-version=8.1
+
+# Clear Gradle cache
+rm -rf ~/.gradle/caches/
+./gradlew clean
+```
+
+### Issue: Python dependencies not found
+
+**Error**: "Could not install requirements"
+
+**Solution**:
+```bash
+# Ensure Python 3.10+ is in PATH
+which python3
+python3 --version
+
+# Update build.gradle to use correct Python
+python {
+ buildPython "/usr/bin/python3.10" # Explicit version
+ // ...
+}
+```
+
+### Issue: NDK not found
+
+**Error**: "NDK is not configured"
+
+**Solution**:
+1. Open Android Studio
+2. Tools → SDK Manager → SDK Tools
+3. Check "NDK (Side by side)"
+4. Click Apply to install
+5. Update `local.properties`:
+ ```properties
+ ndk.dir=/path/to/sdk/ndk/25.1.8937393
+ ```
+
+### Issue: Out of memory during build
+
+**Error**: "Java heap space"
+
+**Solution**:
+Edit `gradle.properties`:
+```properties
+org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
+```
+
+### Issue: Build succeeds but APK doesn't install
+
+**Error**: "INSTALL_FAILED_UPDATE_INCOMPATIBLE"
+
+**Solution**:
+```bash
+# Uninstall old version first
+adb uninstall ai.ciris.mobile
+
+# Then install new APK
+adb install app/build/outputs/apk/debug/app-debug.apk
+```
+
+## Build Performance Tips
+
+### Speed Up Builds
+
+1. **Enable Gradle daemon** (gradle.properties):
+ ```properties
+ org.gradle.daemon=true
+ org.gradle.parallel=true
+ org.gradle.configureondemand=true
+ ```
+
+2. **Use build cache**:
+ ```bash
+ ./gradlew assembleDebug --build-cache
+ ```
+
+3. **Skip tests for faster builds**:
+ ```bash
+ ./gradlew assembleDebug -x test -x lint
+ ```
+
+### Reduce APK Size
+
+1. **Enable resource shrinking** (build.gradle):
+ ```gradle
+ buildTypes {
+ release {
+ shrinkResources true
+ minifyEnabled true
+ }
+ }
+ ```
+
+2. **Use APK splits** for different architectures:
+ ```gradle
+ splits {
+ abi {
+ enable true
+ reset()
+ include "arm64-v8a", "armeabi-v7a"
+ universalApk false
+ }
+ }
+ ```
+
+3. **Analyze APK size**:
+ ```bash
+ ./gradlew assembleRelease
+ # Then in Android Studio: Build → Analyze APK
+ ```
+
+## Testing the Build
+
+### Run Unit Tests
+
+```bash
+./gradlew test
+```
+
+### Run Instrumentation Tests
+
+```bash
+# On connected device
+./gradlew connectedAndroidTest
+
+# On specific device
+adb devices
+./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.deviceId=
+```
+
+### Verify APK Contents
+
+```bash
+# Unzip APK to inspect
+unzip -l app/build/outputs/apk/debug/app-debug.apk
+
+# Check Python files are included
+unzip -l app/build/outputs/apk/debug/app-debug.apk | grep "\.pyc"
+
+# Check GUI static assets
+unzip -l app/build/outputs/apk/debug/app-debug.apk | grep "gui_static"
+```
+
+## Build for Different Targets
+
+### ARM64 Only (Modern Devices)
+
+```gradle
+ndk {
+ abiFilters "arm64-v8a"
+}
+```
+
+### ARM32 + ARM64 (Wider Compatibility)
+
+```gradle
+ndk {
+ abiFilters "arm64-v8a", "armeabi-v7a"
+}
+```
+
+### All Architectures (Maximum Compatibility)
+
+```gradle
+ndk {
+ abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
+}
+```
+
+## CI/CD Integration
+
+### GitHub Actions Example
+
+```yaml
+name: Build Android APK
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+
+ - name: Build Debug APK
+ run: |
+ cd android
+ chmod +x gradlew
+ ./gradlew assembleDebug
+
+ - name: Upload APK
+ uses: actions/upload-artifact@v3
+ with:
+ name: app-debug
+ path: android/app/build/outputs/apk/debug/app-debug.apk
+```
+
+## Next Steps
+
+After successful build:
+
+1. **Test on device**: Install and verify basic functionality
+2. **Configure LLM endpoint**: Settings → Enter OpenAI-compatible URL
+3. **Test API**: `adb shell curl http://127.0.0.1:8000/v1/health`
+4. **Review logs**: `adb logcat -s CIRISMobile`
+
+## Support
+
+- **Build issues**: Check [Troubleshooting](#troubleshooting-build-issues)
+- **Chaquopy docs**: https://chaquo.com/chaquopy/doc/current/
+- **Android docs**: https://developer.android.com/studio/build
+- **CIRIS issues**: https://github.com/CIRISAI/CIRISAgent/issues
diff --git a/android/README.md b/android/README.md
new file mode 100644
index 0000000000..8039cd895a
--- /dev/null
+++ b/android/README.md
@@ -0,0 +1,691 @@
+# CIRIS Android - 100% On-Device Packaging
+
+**Architecture**: All Python code and UI run on-device. Only LLM inference is remote.
+
+## What Runs Where
+
+### On-Device (Android App)
+- ✅ Python 3.10+ runtime (via Chaquopy)
+- ✅ Complete CIRIS Python codebase
+- ✅ FastAPI server (localhost:8000)
+- ✅ Web UI (bundled assets in WebView)
+- ✅ SQLite database
+- ✅ All business logic, agents, tools
+
+### Remote (OpenAI-Compatible Endpoint)
+- ☁️ LLM inference only
+- ☁️ Supports: OpenAI, Together.ai, local LLMs, any OpenAI-compatible API
+
+### NOT Included
+- ❌ No ciris.ai cloud components
+- ❌ No cloud sync
+- ❌ No external dependencies except LLM endpoint
+
+## Build Requirements
+
+- **Android Studio**: Hedgehog (2023.1.1) or newer
+- **JDK**: 17 or higher
+- **Python**: 3.10 or higher (for building)
+- **Gradle**: 8.0+ (included in Android Studio)
+- **Min Android SDK**: 24 (Android 7.0+)
+- **Target Android SDK**: 34 (Android 14)
+
+## Quick Start
+
+### 1. Install Dependencies
+
+```bash
+# Install Python dependencies for Chaquopy
+pip install chaquopy
+
+# Ensure you have Android SDK and NDK installed
+# Via Android Studio SDK Manager:
+# - Android SDK Platform 34
+# - Android SDK Build-Tools
+# - NDK (Side by side)
+```
+
+### 2. Build the APK
+
+```bash
+cd android
+./gradlew assembleDebug
+
+# APK output: app/build/outputs/apk/debug/app-debug.apk
+```
+
+### 3. Install on Device
+
+```bash
+adb install app/build/outputs/apk/debug/app-debug.apk
+```
+
+### 4. Configure LLM Endpoint
+
+On first launch:
+1. Open Settings (menu → Settings)
+2. Enter your LLM endpoint:
+ - **ciris.ai** (recommended): `https://ciris.ai/v1` + Google Sign-In (see below)
+ - **OpenAI**: `https://api.openai.com/v1` + your API key
+ - **Together.ai**: `https://api.together.ai/v1` + your API key
+ - **Local LLM**: `http://192.168.1.100:8080/v1` + any key
+3. Save and restart the app
+
+### 5. ciris.ai LLM Proxy (Recommended for Android)
+
+The ciris.ai proxy uses Google Sign-In for authentication, eliminating the need to manage API keys:
+
+1. **First Launch**: Tap "Sign in with Google" on the setup screen
+2. **Authentication**: Complete Google Sign-In flow
+3. **Auto-Configuration**: The app automatically configures:
+ - Endpoint: `https://ciris.ai/v1`
+ - API Key: Your Google ID token (auto-refresh)
+
+**Token Refresh Flow** (handled automatically):
+
+```
+┌────────────────────────────────────────────────────────────────┐
+│ Token Refresh Cycle │
+├────────────────────────────────────────────────────────────────┤
+│ 1. Python LLM service receives 401 from ciris.ai │
+│ 2. Python writes `.token_refresh_needed` signal file │
+│ 3. Android TokenRefreshManager detects signal (polls 10s) │
+│ 4. Android calls silentSignIn() to get fresh Google ID token │
+│ 5. Android updates .env with new OPENAI_API_KEY │
+│ 6. Android writes `.config_reload` signal file │
+│ 7. Python ResourceMonitor detects signal (polls 1s) │
+│ 8. Python reloads .env, emits token_refreshed signal │
+│ 9. Python LLM service resets circuit breaker, reinits client │
+│ 10. Retry LLM request with fresh token │
+└────────────────────────────────────────────────────────────────┘
+```
+
+**Key Files**:
+- `auth/TokenRefreshManager.kt`: Android token refresh polling
+- `ciris_engine/logic/services/infrastructure/resource_monitor/service.py`: Signal detection
+- `ciris_engine/logic/services/runtime/llm_service/service.py`: Circuit breaker reset
+
+## Architecture Details
+
+### Python Runtime (Chaquopy)
+
+The app uses [Chaquopy](https://chaquo.com/chaquopy/) to embed Python 3.10 in the Android APK:
+
+- **Entry Point**: `mobile_main.py` launches the FastAPI server
+- **Dependencies**: Bundled via `pip {}` block in `app/build.gradle`
+- **Source Code**: Complete CIRIS codebase included in APK
+- **Runtime**: Single-threaded, optimized for <500MB RAM
+
+### FastAPI Server
+
+Runs on `localhost:8000` within the app:
+
+```python
+# mobile_main.py
+async def start_mobile_server():
+ config = uvicorn.Config(
+ app,
+ host="127.0.0.1",
+ port=8000,
+ workers=1, # Low-resource optimization
+ log_level="warning",
+ )
+ server = uvicorn.Server(config)
+ await server.serve()
+```
+
+### WebView UI
+
+The bundled CIRIS web UI loads in a WebView pointing to `http://127.0.0.1:8000`:
+
+- **Source**: `ciris_engine/gui_static/*` bundled as assets
+- **JavaScript**: Fully enabled for interactive UI
+- **Storage**: DOM storage and database enabled for client state
+- **Navigation**: All links stay within WebView
+
+### LLM Integration
+
+All LLM calls route to the configured remote endpoint:
+
+```kotlin
+// SettingsActivity.kt saves these to environment
+System.setProperty("OPENAI_API_BASE", apiBase)
+System.setProperty("OPENAI_API_KEY", apiKey)
+```
+
+Python code reads these via `os.environ` and uses `httpx` to call the remote API.
+
+## Performance Optimization
+
+### Memory (<500MB Target)
+
+- Single Uvicorn worker (`workers=1`)
+- No auto-reload
+- Minimal logging (`LOG_LEVEL=WARNING`)
+- SQLite with WAL mode for concurrency
+- No large ML models on-device
+
+### Battery
+
+- Server runs only when app is in foreground
+- Disable SSE streaming when backgrounded
+- Connection pooling with short timeouts
+- Optional: Stop server on background, restart on resume
+
+### Storage
+
+- App size: ~50MB (Python runtime + dependencies)
+- Database: <10MB typical usage
+- Logs: Rotate daily, max 7 days
+- Total footprint: <100MB
+
+## Testing
+
+### Local Testing
+
+```bash
+# Run Python server locally to verify
+python mobile_main.py
+# Visit http://127.0.0.1:8000
+
+# Run Android instrumentation tests
+./gradlew connectedAndroidTest
+```
+
+### On-Device Smoke Test
+
+```bash
+# Via ADB shell
+adb shell
+
+# Test API is responding
+curl http://127.0.0.1:8000/v1/health
+
+# Test LLM integration
+curl -X POST http://127.0.0.1:8000/v1/chat/completions \
+ -H "Authorization: Bearer $YOUR_API_KEY" \
+ -d '{"messages":[{"role":"user","content":"ping"}]}'
+```
+
+## Build Variants
+
+### Debug Build
+
+```bash
+./gradlew assembleDebug
+```
+
+- Includes debug symbols
+- Enables logging
+- No code obfuscation
+
+### Release Build
+
+```bash
+./gradlew assembleRelease
+```
+
+- Optimized and minified
+- ProGuard enabled (configure in `proguard-rules.pro`)
+- Requires signing configuration
+
+## Configuration
+
+### Environment Variables
+
+Set in `mobile_main.py` or via Android settings:
+
+- `OPENAI_API_BASE`: LLM endpoint URL (e.g., `https://ciris.ai/v1`)
+- `OPENAI_API_KEY`: LLM API key (Google ID Token for ciris.ai, auto-refreshed)
+- `CIRIS_OFFLINE_MODE`: Always `true` (no cloud sync)
+- `CIRIS_MAX_WORKERS`: `1` (single worker)
+- `CIRIS_LOG_LEVEL`: `WARNING` (reduce overhead)
+- `CIRIS_HOME`: App data directory (set automatically by `setup_android_environment()`)
+
+**ciris.ai specific** (managed by TokenRefreshManager):
+- `.token_refresh_needed`: Signal file written by Python when 401 received
+- `.config_reload`: Signal file written by Android after token refresh
+- `.env`: Contains OPENAI_API_KEY (Google ID Token) and endpoint config
+
+### SharedPreferences
+
+Persisted settings in `SettingsActivity`:
+
+- `openai_api_base`: User-configured LLM endpoint
+- `openai_api_key`: User-configured API key
+- `billing_api_url`: CIRISBilling server URL
+- `google_user_id`: User's Google account ID (for billing)
+
+## Google Play Billing Integration
+
+The app supports in-app purchases via Google Play for buying CIRIS credits.
+
+### Architecture
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Android Device │
+├─────────────────────────────────────────────────────────────┤
+│ CIRIS Agent (on-device) │ Google Play Billing Client │
+│ - FastAPI @ localhost:8000 │ - BillingClient SDK 7.1.1 │
+│ - Python via Chaquopy │ - Purchase flow UI │
+│ - SQLite DB │ - Returns purchaseToken │
+└──────────────┬───────────────┴───────────────┬──────────────┘
+ │ │
+ │ LLM Requests │ Verify Token
+ ▼ ▼
+┌──────────────────────────┐ ┌──────────────────────────────┐
+│ Remote LLM Provider │ │ CIRISBilling API │
+│ (OpenAI-compatible) │ │ POST /google-play/verify │
+└──────────────────────────┘ └──────────────────────────────┘
+```
+
+### Available Products
+
+Products must be configured in Google Play Console with these exact IDs:
+
+| Product ID | Credits | Description |
+|--------------|---------|-------------|
+| credits_100 | 100 | 100 Credits |
+| credits_250 | 250 | 250 Credits |
+| credits_600 | 600 | 600 Credits |
+
+### Purchase Flow
+
+1. User taps "Buy Credits" in the app menu
+2. User selects a credit package
+3. Google Play purchase flow launches
+4. On success, app sends `purchaseToken` to CIRISBilling server
+5. Server verifies token with Google Play Developer API
+6. Server grants credits to user's account (idempotent)
+7. Server acknowledges purchase with Google
+8. App displays new balance
+
+### Key Files
+
+- `billing/BillingManager.kt`: Google Play Billing client wrapper
+- `billing/BillingApiClient.kt`: HTTP client for CIRISBilling API
+- `PurchaseActivity.kt`: Credit purchase UI
+
+### Setup for Production
+
+1. **Google Play Console**: Create products matching the IDs above
+2. **CIRISBilling Server**: Deploy with Google Play credentials
+3. **App Config**: Set `billing_api_url` to your CIRISBilling URL
+
+### Testing Purchases
+
+Use Google Play's license testing:
+1. Add test accounts in Google Play Console
+2. Test with sandbox purchases (no real charges)
+3. Verify idempotency by re-submitting tokens
+
+## Development & Debugging
+
+### Build Commands
+
+```bash
+# Set Java version (required for Gradle)
+export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
+
+# Build debug APK
+cd /home/emoore/CIRISAgent/android
+./gradlew assembleDebug
+# Output: app/build/outputs/apk/debug/app-debug.apk
+
+# Build release APK
+./gradlew assembleRelease
+# Output: app/build/outputs/apk/release/app-release.apk
+
+# Clean build
+./gradlew clean assembleRelease
+```
+
+### Build & Deploy Scripts
+
+Located in `android/scripts/`, these scripts automate common development tasks:
+
+#### deploy-debug.sh - Quick Debug Deploy
+
+Build and deploy debug APK to connected device:
+
+```bash
+cd /home/emoore/CIRISAgent/android
+
+# Full build and deploy (rebuilds web assets + APK)
+./scripts/deploy-debug.sh
+
+# Skip web asset rebuild (faster, use when only Kotlin changed)
+./scripts/deploy-debug.sh --skip-web
+
+# Skip build, deploy existing APK
+./scripts/deploy-debug.sh --skip-web --skip-build
+
+# Skip install, just build
+./scripts/deploy-debug.sh --skip-install
+```
+
+**Features**:
+- Auto-detects ADB path (Windows via WSL or native Linux)
+- Sets correct JAVA_HOME
+- Copies web assets from Next.js build
+- Builds debug APK
+- Installs and launches on device
+- Shows helpful log commands after deploy
+
+#### full-rebuild.sh - Complete Clean Build
+
+Performs a complete rebuild including web assets and optional pydantic wheels:
+
+```bash
+# Standard full rebuild
+./scripts/full-rebuild.sh
+
+# Include pydantic wheel rebuild (takes longer)
+./scripts/full-rebuild.sh --rebuild-wheels
+
+# Build release instead of debug
+./scripts/full-rebuild.sh --release
+
+# Skip web rebuild
+./scripts/full-rebuild.sh --skip-web
+```
+
+**Build Steps**:
+1. Cleans Gradle cache
+2. Rebuilds Next.js web UI (`npm run build`)
+3. Copies assets to all 3 locations
+4. Optionally rebuilds pydantic wheels for ARM64
+5. Builds APK (debug or release)
+
+#### pull-device-logs.sh - Collect Device Logs
+
+Pull all logs from device for debugging:
+
+```bash
+# Pull all logs to /tmp/ciris-logs/YYYYMMDD_HHMMSS/
+./scripts/pull-device-logs.sh
+
+# Live tail Python logs
+./scripts/pull-device-logs.sh --live
+
+# Specify output directory
+./scripts/pull-device-logs.sh --output /path/to/logs
+```
+
+**Collected Files**:
+- `logs/` - All Python log files
+- `logcat_python.txt` - Python stdout/stderr from logcat
+- `logcat_crashes.txt` - AndroidRuntime crash logs
+- `logcat_full.txt` - Complete logcat dump
+- `databases/` - SQLite databases
+- `shared_prefs/` - SharedPreferences XML files
+- `app_info.txt` - Device/app version info
+
+**Note**: For debug builds, uses `run-as` to access private app data. Release builds can only pull logcat.
+
+### ADB Commands (Windows via WSL)
+
+```bash
+# ADB path on Windows (accessed from WSL)
+ADB="/mnt/c/Users/moore/AppData/Local/Android/Sdk/platform-tools/adb.exe"
+
+# List connected devices
+$ADB devices -l
+
+# Target specific device (Samsung example)
+$ADB -s R5CRC3BWLRZ
+
+# Install APK
+$ADB install "$(wslpath -w /home/emoore/CIRISAgent/android/app/build/outputs/apk/release/app-release.apk)"
+
+# Uninstall (clears all data including database)
+$ADB uninstall ai.ciris.mobile
+
+# Launch app
+$ADB shell monkey -p ai.ciris.mobile -c android.intent.category.LAUNCHER 1
+
+# Clear logcat and start fresh
+$ADB logcat -c
+```
+
+### Log File Locations (On-Device)
+
+All logs are stored in the app's private data directory:
+
+```
+/data/data/ai.ciris.mobile/files/ciris/
+├── logs/
+│ ├── latest.log # Symlink to current day's log
+│ ├── incidents_latest.log # Symlink to current day's incidents
+│ ├── ciris_agent_YYYYMMDD_HHMMSS.log # Full application log
+│ └── incidents_YYYYMMDD_HHMMSS.log # Warnings/errors only
+├── data/
+│ └── ciris_engine.db # SQLite database
+└── .env # Configuration (created after setup)
+```
+
+### Reading Logs via ADB
+
+```bash
+ADB="/mnt/c/Users/moore/AppData/Local/Android/Sdk/platform-tools/adb.exe"
+DEVICE="R5CRC3BWLRZ" # Samsung device ID
+
+# Read latest application log
+$ADB -s $DEVICE shell "run-as ai.ciris.mobile cat /data/data/ai.ciris.mobile/files/ciris/logs/latest.log" | tail -200
+
+# Read incidents log (warnings/errors only)
+$ADB -s $DEVICE shell "run-as ai.ciris.mobile cat /data/data/ai.ciris.mobile/files/ciris/logs/incidents_latest.log" | tail -100
+
+# Search for specific patterns
+$ADB -s $DEVICE shell "run-as ai.ciris.mobile cat /data/data/ai.ciris.mobile/files/ciris/logs/latest.log" | grep -iE "oauth|setup|error"
+
+# List all log files
+$ADB -s $DEVICE shell "run-as ai.ciris.mobile ls -la /data/data/ai.ciris.mobile/files/ciris/logs/"
+```
+
+### Android Logcat (Native/WebView Logs)
+
+```bash
+ADB="/mnt/c/Users/moore/AppData/Local/Android/Sdk/platform-tools/adb.exe"
+
+# All CIRIS-related logs
+$ADB logcat -d 2>&1 | grep -i "CIRIS\|CIRISMobile"
+
+# WebView console logs (JavaScript)
+$ADB logcat -d 2>&1 | grep "chromium.*CONSOLE"
+
+# Setup wizard logs
+$ADB logcat -d 2>&1 | grep "chromium.*Setup"
+
+# Native auth injection logs
+$ADB logcat -d 2>&1 | grep "CIRISMobile.*Inject"
+
+# Filter by tag
+$ADB logcat -s CIRISMobile:V
+```
+
+### Rebuilding Web UI (Next.js Static Assets)
+
+When modifying the web UI, you must rebuild and copy assets:
+
+```bash
+# 1. Build Next.js static export
+cd /home/emoore/CIRISAgent/android/.web-build/CIRISGUI-Android/apps/agui
+npm run build
+
+# 2. Copy to android_gui_static
+rm -rf /home/emoore/CIRISAgent/android_gui_static/*
+cp -r out/* /home/emoore/CIRISAgent/android_gui_static/
+
+# 3. Copy to Android assets
+rm -rf /home/emoore/CIRISAgent/android/app/src/main/assets/public/*
+cp -r /home/emoore/CIRISAgent/android_gui_static/* /home/emoore/CIRISAgent/android/app/src/main/assets/public/
+
+# 4. Rebuild APK
+cd /home/emoore/CIRISAgent/android
+export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
+./gradlew assembleRelease
+```
+
+### Key Source Files
+
+**Android Native (Kotlin)**:
+- `app/src/main/java/ai/ciris/mobile/MainActivity.kt` - Main activity, WebView, auth injection
+- `app/src/main/java/ai/ciris/mobile/auth/LoginActivity.kt` - Google Sign-In flow
+- `app/src/main/java/ai/ciris/mobile/billing/BillingManager.kt` - Google Play Billing
+
+**Python Backend**:
+- `ciris_engine/logic/adapters/api/routes/setup.py` - Setup wizard API
+- `ciris_engine/logic/adapters/api/routes/auth.py` - OAuth/auth endpoints
+- `ciris_engine/logic/setup/first_run.py` - First-run detection
+
+**Web UI (Next.js)**:
+- `android/.web-build/CIRISGUI-Android/apps/agui/app/setup/page.tsx` - Setup wizard
+- `android/.web-build/CIRISGUI-Android/apps/agui/lib/ciris-sdk/` - TypeScript SDK
+
+### Log Collection Script
+
+Use the script at `/tmp/collect_ciris_logs.sh` to collect all logs for analysis:
+
+```bash
+/tmp/collect_ciris_logs.sh
+# Logs saved to /tmp/ciris_logs_YYYYMMDD_HHMMSS/
+```
+
+## Troubleshooting
+
+### Server Won't Start
+
+**Symptom**: WebView shows "Server Error"
+
+**Solutions**:
+1. Check logcat: `adb logcat -s CIRISMobile`
+2. Verify Python dependencies in `app/build.gradle`
+3. Ensure LLM endpoint is configured
+4. Try clean rebuild: `./gradlew clean assembleDebug`
+
+### WebView Shows Blank Screen
+
+**Symptom**: App loads but WebView is empty
+
+**Solutions**:
+1. Check server is running: `adb shell curl http://127.0.0.1:8000`
+2. Verify UI assets bundled: Check `app/build/intermediates/assets/`
+3. Enable WebView debugging: `WebView.setWebContentsDebuggingEnabled(true)`
+4. Check Chrome DevTools: `chrome://inspect`
+
+### LLM Calls Failing
+
+**Symptom**: Chat doesn't respond, errors in logs
+
+**Solutions**:
+1. Verify endpoint in Settings matches your LLM provider
+2. Check API key is valid
+3. Test endpoint directly: `curl $OPENAI_API_BASE/models`
+4. Check network permissions in AndroidManifest.xml
+5. For LAN endpoints, ensure device is on same network
+
+### ciris.ai Token Refresh Issues
+
+**Symptom**: 401 errors persist, LLM calls keep failing
+
+**Solutions**:
+1. Check Google Sign-In is active: `adb logcat -s GoogleSignIn`
+2. Verify signal files exist: `ls $CIRIS_HOME/.token_refresh_needed .config_reload`
+3. Check TokenRefreshManager is polling: `adb logcat -s TokenRefreshManager`
+4. Force fresh sign-in: Settings → Sign Out → Sign In Again
+5. Check circuit breaker state in ResourceMonitor logs
+6. Verify .env has OPENAI_API_KEY after refresh
+
+**Symptom**: Token refreshes but calls still fail
+
+**Solutions**:
+1. Check the circuit breaker cooldown (5 minutes for billing errors)
+2. Verify LLM client was reinitialized: `adb logcat -s LLMService`
+3. Clear app data and re-authenticate
+
+### High Memory Usage
+
+**Symptom**: App crashes or slows down
+
+**Solutions**:
+1. Reduce max workers to 1 (should be default)
+2. Lower log level to ERROR
+3. Clear database: Settings → Clear Data
+4. Disable unnecessary adapters in startup
+5. Profile with Android Profiler
+
+### Billing Issues
+
+**Symptom**: Purchase fails or credits not added
+
+**Solutions**:
+1. Check Google account is signed in
+2. Verify billing endpoint in logs: `adb logcat -s CIRISBillingAPI`
+3. Test server connectivity: `curl https://billing.ciris.ai/health`
+4. Check purchase wasn't already processed (idempotent)
+5. For test purchases, use license testers in Play Console
+
+**Symptom**: Products not loading
+
+**Solutions**:
+1. Verify products exist in Google Play Console with exact IDs
+2. Check Play Billing connection: `adb logcat -s CIRISBilling`
+3. Ensure app is signed with correct certificate
+4. Wait 24h after creating products for propagation
+
+## Security Considerations
+
+### API Key Storage
+
+- Stored in SharedPreferences (encrypted on API 23+)
+- Never logged or transmitted except to configured LLM endpoint
+- User responsible for securing their device
+
+### Network Security
+
+For production:
+1. Use TLS endpoints only (`https://`)
+2. For LAN endpoints, pin certificates in `network_security_config.xml`
+3. Disable cleartext traffic except for localhost
+
+### Code Obfuscation
+
+In `proguard-rules.pro`:
+```proguard
+# Keep Python-Java bridge
+-keep class com.chaquo.python.** { *; }
+
+# Keep CIRIS models
+-keep class ai.ciris.mobile.** { *; }
+```
+
+## Deployment Checklist
+
+- [ ] Configure release signing in `app/build.gradle`
+- [ ] Set production LLM endpoint
+- [ ] Enable ProGuard for release builds
+- [ ] Test on target devices (min SDK 24)
+- [ ] Verify memory usage <500MB
+- [ ] Test offline capability (LLM remote but UI/logic on-device)
+- [ ] Document LLM endpoint setup for users
+- [ ] Prepare Google Play Store assets
+- [ ] Configure in-app products in Google Play Console
+- [ ] Deploy CIRISBilling server with Google Play credentials
+- [ ] Test sandbox purchases with license testers
+- [ ] Verify purchase verification flow end-to-end
+
+## License
+
+Same as CIRIS: Apache 2.0
+
+## Support
+
+- **Issues**: GitHub Issues
+- **Docs**: `/android/README.md` (this file)
+- **Source**: `android/` directory
+
+---
+
+**Remember**: 100% of Python and UI runs on-device. Only LLM inference is remote. No ciris.ai cloud components.
diff --git a/android/android_gui_static/11steps.svg b/android/android_gui_static/11steps.svg
new file mode 100644
index 0000000000..6bb743f543
--- /dev/null
+++ b/android/android_gui_static/11steps.svg
@@ -0,0 +1,107 @@
+
diff --git a/android/android_gui_static/2x-schematics.png b/android/android_gui_static/2x-schematics.png
new file mode 100644
index 0000000000..7a2579dad4
Binary files /dev/null and b/android/android_gui_static/2x-schematics.png differ
diff --git a/android/android_gui_static/404.html b/android/android_gui_static/404.html
new file mode 100644
index 0000000000..e3ae89a712
--- /dev/null
+++ b/android/android_gui_static/404.html
@@ -0,0 +1 @@
+404: This page could not be found.
404
This page could not be found.
diff --git a/android/android_gui_static/404/index.html b/android/android_gui_static/404/index.html
new file mode 100644
index 0000000000..e3ae89a712
--- /dev/null
+++ b/android/android_gui_static/404/index.html
@@ -0,0 +1 @@
+404: This page could not be found.