Skip to content

Kern documentation

Everything about how Kern works: the agent loop and its tools, the permission model, the terminal stack, the workspace model, and how the project is put together.

Installation

Kern ships as a GitHub release rather than through the Play Store, so installation is a direct APK download. Pick the build for your architecture:

BuildDeviceUbuntu
app-arm64-v8a-release.apkMost modern phonesYes
app-x86_64-release.apkx86_64 emulatorsYes
app-armeabi-v7a-release.apkOlder 32-bit phonesNo
app-x86-release.apkx86 emulatorsNo
app-universal-release.apkAny of the aboveYes
adb install -r app-arm64-v8a-release.apk

Every release is signed with the same key, so a later version installs straight over an existing copy and keeps your workspaces, sessions and API keys.

Requirements

  • Android 9, API 28, or newer.
  • arm64-v8a, armeabi-v7a, x86 or x86_64.
  • About 45 MB for the app, plus roughly 375 MB for the Ubuntu environment.
  • An OpenAI-compatible endpoint and a key, or a local model.

The agent loop

A turn is a sequence of steps. The agent builds a context from the conversation, the workspace and its own memory, asks the model what to do next, and executes the tool calls that come back. It repeats until the model stops asking for tools.

Each step follows the same shape:

  1. The context is assembled under a byte budget, with older turns folded into a digest.
  2. The request is streamed from the provider, and deltas are shown as they arrive.
  3. Tool calls are split into two groups by whether they are read-only.
  4. Read-only calls run concurrently. Mutating calls run one at a time, behind approval.
  5. Results are reordered to match the order the model asked for them in.
  6. The loop repeats, until the model returns without requesting a tool.

If a provider call fails and produced nothing, the turn is retried with a backoff. If the model returns an empty reply, it is nudged once. If it stops because it hit a token limit, it is asked to continue, up to twice.

A failing tool never ends the turn. It comes back as an error result the model can read and act on, which is what lets it recover on its own.

The tool set

Twenty-nine tools, in eight groups.

ToolWhat it doesRead only
list_filesList a directoryYes
read_fileRead a file, with a byte capYes
search_filesSearch file contentsYes
globMatch paths by patternYes
grepSearch with a regular expressionYes
fetch_urlFetch a URL, with secrets redactedYes
propose_editPropose a snippet replacementNo
edit_fileReplace an exact snippetNo
write_fileWrite a whole fileNo
delete_fileDelete a fileNo
propose_patchPropose a multi-file patchNo
run_buildRun the project buildNo
run_testsRun the test suiteNo
run_lintRun static analysisNo
run_commandRun a shell commandNo
terminal_statusReport the live session stateYes
git_statusWorking tree statusYes
git_diffShow the current diffYes
git_commitCreate a commitNo
update_todosMaintain the task listNo
propose_planPresent a plan for reviewNo
rememberWrite a durable memory noteNo
server_startStart a dev serverNo
server_stopStop a dev serverNo
preview_openOpen the live previewNo
preview_statusReport preview stateNo
preview_consoleRead the page consoleNo
preview_evalEvaluate JavaScript in the pageNo
preview_screenshotCapture the pageNo

A read-only tool may run alongside the other read-only tools in the same step. Anything that writes is sequenced and gated by the permission mode.

Arguments are repaired centrally. If a model sends file_path where the schema says path, or wraps everything in an input object, or sends a number as a string, the repair layer fixes it in one place instead of in twenty-nine tools.

Permission modes

ModeReadsEdits and commandsDestructive
Ask changesAutomaticAlways asksAlways asks
Auto editAutomaticAutomaticAlways asks
Full accessAutomaticAutomaticAlways asks

The mode is chosen from the chip above the composer and applies to the whole session. Destructive commands are excluded from every mode, without exception.

Command classification

Every shell command is classified before it runs, by a conservative pattern matcher:

ImpactExamplesBehaviour
Read-onlyls grep catRuns freely
Mutatingnpm install git addGated by the mode
Networkcurl npm publishGated by the mode
Destructiverm -rf mkfs git reset --hardAlways asks

This is a safety signal, not a security boundary. The classifier never executes anything, and it errs toward a higher impact when it is unsure. It exists to catch accidents, not to stop a determined attacker.

Approvals and plans

When a step needs a decision, the turn pauses and the UI shows a sheet:

  • Command approval shows the exact command, its working directory and the reason the agent wants it.
  • Edit approval shows the diff before anything is written.
  • Plan review shows a multi-step plan, which can be approved, rejected or sent back for changes.

A patch that touches several files can be approved file by file, so you can take the half you agree with.

Context and memory

The agent works from a context budget rather than the whole history. When the replayed conversation grows past the budget, the oldest tool results are dropped and the older turns are folded into a digest, while the most recent tool results are kept intact.

Three sources feed the context:

  • The system prompt, which describes every tool and the behaviour rules.
  • Project rules, read from a KERN.md file at the workspace root, if one exists.
  • Workspace memory, a per-workspace file the agent can write to, holding plans, notes and an archive.

The terminal stack

Kern's terminal is four layers, each replaceable on its own.

LayerModuleWhat it does
Contractcore:terminal:apiThe TerminalRuntime interface every implementation satisfies
Emulatorcore:terminal:enginePure-Kotlin VT and ANSI emulation, a plain JVM module
PTYcore:terminal:ptyThe native pseudo-terminal, in C with CMake and the NDK
Runtimecore:terminal:implementationDevice shell and proot backends, sessions, the distro installer

The Linux environment

The Ubuntu environment is installed on demand from a catalog that is data rather than code. The catalog pins a rootfs URL, its size and its SHA-256 per ABI.

The install runs in five stages:

  1. Download, with the progress reported against the compressed size.
  2. Verify the SHA-256 against the catalog.
  3. Extract into a staging folder, reporting unpacked bytes rather than a flat zero.
  4. Probe that proot can actually start in this environment.
  5. Promote the staged folder into place, atomically.

Because the promotion is the last step and atomic, an interrupted or failed install never damages a working environment.

The binaries are pinned and verified. The vendored proot runtime is committed to the repository, recorded in a lock file with its size and SHA-256, and checked on every build. A tampered binary fails the build rather than the device.

The native PTY

Android's bionic has no reliable combination of forkpty and login_tty, so the pseudo-terminal is opened by hand:

posix_openpt()  ->  grantpt()  ->  unlockpt()  ->  ptsname()
fork()
  child:  setsid()
          ioctl(slave, TIOCSCTTY)
          dup2() onto 0, 1, 2
          execve()
  parent: keeps the master descriptor

Because the child gets a controlling terminal, job control, interactive prompts and full-screen programs all behave the way they should. Reads use poll with a short timeout, so a cancelled coroutine is noticed promptly rather than hanging on a blocking read.

The VT emulator

The emulator is a plain kotlin-jvm module, deliberately not an Android library, so it can be tested on the JVM.

  • A byte-to-action state machine covering CSI, OSC and DCS sequences, tolerant of a sequence split across reads.
  • Primary and alternate screen buffers, with a bounded scrollback.
  • Scroll regions, saved cursor, wrap handling and DEC private modes.
  • 256-colour and truecolour SGR, and correct widths for East-Asian characters.
  • Response bytes for DSR, DA and OSC colour queries, which makes the terminal bidirectional rather than a renderer.

The package manager

The package screen drives apt inside the guest over the shared command executor. The catalogue comes from apt-cache pkgnames and the installed list from dpkg --get-selections.

Installing is not a blocking call. The agent streams the output and the app shows it live in a log screen, so a large install is visible rather than silent.

Workspace types

TypeWhere it livesShared with the terminal
App-managedPrivate app storageYes, directly
SAF, with All files accessIts real /sdcard pathYes, directly
SAF, without All files accessA private execution mirrorThrough the mirror, with a manual sync back

The two concepts are separate on purpose. A logical workspace describes the folder you chose. An execution workspace describes where the commands actually run. They differ precisely when a SAF folder cannot be reached at its real path.

Storage and permissions

All files access arrives in Android 11, API 30. On Android 9 and 10 the permission does not exist, and Kern does not offer a grant prompt that could not work. The SAF adapter and the execution mirror cover those versions instead.

Paths are contained, never absolute:

WorkspacePaths.normalize("/etc/passwd")      -> null
WorkspacePaths.normalize("../../secrets.txt") -> null

Files and the editor

The file explorer browses the workspace through the same adapter the agent uses, so there is exactly one view of the tree. The editor is a plain Compose text surface with a line-numbered gutter, a dirty flag and a 16 MB read cap.

Providers

The provider layer is split in two. core:providers:api holds the contracts and stays provider-neutral, so no provider's own data shapes leak out of it. core:providers:openai holds an adapter for anything speaking the OpenAI chat completions API.

Two details worth knowing:

  • Streaming tool calls arrive out of order, so they are accumulated by index and reassembled before the agent sees them.
  • The connection test probes the chat endpoint with a one-token request, not the models endpoint, because some gateways answer the models list without checking the credential.

Live preview

The preview is a WebView pointed at the local dev server. Navigation is locked to the loopback interface, and anything else is handed to the system browser.

The JavaScript bridge is deliberately tiny. The only function exposed to the page is a console reporter, so a remote page can never reach it. Screenshots use PixelCopy, because the older canvas path re-runs the software draw and comes back empty from a hardware-rendered WebView.

Appearance

Eleven palettes, light and dark, plus Material 3 dynamic colour on Android 12 and newer. The terminal has seven themes of its own, including a 24-bit truecolour palette, and can follow the app theme or stay fixed.

Notifications and background work

A live terminal session is held by a foreground service that mirrors the session count, and an agent turn is held by a second one, because Android freezes and eventually reclaims a backgrounded process. The turn itself lives in an application-scoped coroutine, so navigating away does not cancel it.

Architecture

Twenty-two Gradle modules with a strict core and feature layering. core:model is pure Kotlin with no Android dependency, and core:terminal:engine is a plain JVM module, so both are unit-testable on the JVM.

ModuleResponsibility
:appNavigation shell, setup wizard, foreground services
:core:modelDomain models and enums, pure Kotlin
:core:commonDispatchers, logging, secret redaction
:core:designsystemTheme tokens, palettes, shared components
:core:dataRoom, DataStore, Keystore credentials
:core:workspaceWorkspace lifecycle, adapters, execution mirror
:core:providers:apiProvider-neutral contracts
:core:providers:openaiOpenAI-compatible streaming adapter
:core:agentThe agent loop, context builder, workspace memory
:core:toolsTools, registry, argument repair
:core:authAPI-key auth coordinator
:core:terminal:apiThe TerminalRuntime contract
:core:terminal:engineVT and ANSI emulator, plain JVM
:core:terminal:ptyNative PTY, C with CMake and the NDK
:core:terminal:binariesVendored proot runtime, SHA-256 pinned
:core:terminal:implementationBackends, sessions, installer
:feature:homeDashboard
:feature:filesFile explorer and code editor
:feature:agentAgent chat, approvals, plan review
:feature:settingsAppearance, provider profiles, model catalogue
:feature:terminalTerminal UI, environment setup, package manager
:feature:previewLive WebView preview and console drawer

Data and storage

Room is at version 4, with nine entities and three explicit migrations. The exported schemas are committed and are the source of truth for those migrations.

The data access objects use @Upsert, never @Insert(REPLACE). Replace deletes and re-inserts, which fires the cascading delete and silently wipes sessions and credentials. A regression test locks this in.

API keys live in a separate table and are encrypted with AES-256-GCM through the Android Keystore. Only ciphertext is stored, and the key never leaves the keystore.

Security

  • Credentials are encrypted with AES-256-GCM and bound to the Keystore.
  • Secrets are stripped from URLs, headers, tool arguments and throwable messages before anything is logged.
  • Android backup and data extraction are disabled, so the credential database cannot leave the device.
  • The preview's JavaScript bridge exposes one console reporter and nothing else.
  • Workspace paths are contained, so a tool cannot escape the workspace with an absolute path or a parent reference.

Testing

466 unit tests across 66 suites, all passing. The stack is JUnit 4, Truth, Robolectric, Turbine, MockK, MockWebServer, Room testing and Compose UI test.

./gradlew testDebugUnitTest

The areas with the deepest coverage are the agent loop, the tool set, the terminal engine and the installer, because those are where a regression is hardest to spot by hand.

Building from source

git clone https://github.com/MurShidM01/Kern.git
cd Kern
./gradlew assembleDebug
./gradlew assembleRelease

The release build produces one APK per ABI plus a universal one, signed with the standard Android debug keystore so that every future version can be installed over the last.

Contributing guidelines, including the conventions for commits and the parts of the codebase that need extra care, are in CONTRIBUTING.md.

License

Kern's own source is MIT licensed. The bundled runtime keeps its own license, and the full texts ship inside the app:

ComponentVersionLicense
proot5.1.107.96GPL-2.0
libtalloc2.5.0LGPL-3.0
libandroid-shmem0.7MIT

Because proot is redistributed under GPL-2.0, the project as a whole is MIT, GPL-2.0 and LGPL-3.0.