mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
Install Android runtime guidance into Codex homes
Install a generated AGENTS.md in the shared Agent Codex home and every per-session Genie home so hosted runtimes pick up the same device-operating guidance at bootstrap. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import com.openai.codex.bridge.HostedCodexConfig
|
||||
import java.io.File
|
||||
import java.io.InterruptedIOException
|
||||
import java.io.IOException
|
||||
@@ -67,7 +68,7 @@ class CodexdForegroundService : Service() {
|
||||
|
||||
val socketPath = intent.getStringExtra(EXTRA_SOCKET_PATH) ?: defaultSocketPath()
|
||||
val codexHome = intent.getStringExtra(EXTRA_CODEX_HOME) ?: defaultCodexHome()
|
||||
File(codexHome).mkdirs()
|
||||
HostedCodexConfig.installAgentsFile(File(codexHome))
|
||||
|
||||
val codexdBinary = resolveCodexdBinary()
|
||||
val args = mutableListOf(
|
||||
|
||||
@@ -33,3 +33,7 @@ android {
|
||||
targetCompatibility = androidJavaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import java.nio.file.Files;
|
||||
|
||||
public final class HostedCodexConfig {
|
||||
public static final String ANDROID_HTTP_PROVIDER_ID = "android-openai-http";
|
||||
private static final String AGENTS_FILENAME = "AGENTS.md";
|
||||
|
||||
private HostedCodexConfig() {}
|
||||
|
||||
public static void write(File codexHome, String baseUrl) throws IOException {
|
||||
if (!codexHome.isDirectory() && !codexHome.mkdirs()) {
|
||||
throw new IOException("failed to create codex home at " + codexHome.getAbsolutePath());
|
||||
}
|
||||
ensureCodexHome(codexHome);
|
||||
installAgentsFile(codexHome);
|
||||
|
||||
String escapedBaseUrl = baseUrl
|
||||
.replace("\\", "\\\\")
|
||||
@@ -29,4 +29,67 @@ public final class HostedCodexConfig {
|
||||
new File(codexHome, "config.toml").toPath(),
|
||||
configToml.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static void installAgentsFile(File codexHome) throws IOException {
|
||||
ensureCodexHome(codexHome);
|
||||
Files.write(
|
||||
new File(codexHome, AGENTS_FILENAME).toPath(),
|
||||
buildAgentsMarkdown().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static String buildAgentsMarkdown() {
|
||||
return """
|
||||
# Android Agent/Genie Runtime Notes
|
||||
|
||||
This Codex runtime is operating on an Android device through the Agent Platform.
|
||||
|
||||
## If you are the Agent
|
||||
|
||||
- The user interacts only with the Agent.
|
||||
- Plan the work, choose the target package or packages, and start one Genie session per target app that needs to be driven.
|
||||
- Delegate objectives, not tool choices. Tell each Genie what outcome it must achieve in its paired app and let the Genie choose its own tools.
|
||||
- Answer Genie questions directly when you can. If the answer depends on user intent or missing constraints, ask the user.
|
||||
- Keep auth, upstream access, and any internet-facing model traffic on the Agent side.
|
||||
|
||||
## If you are a Genie
|
||||
|
||||
- You are paired with exactly one target app sandbox for this session.
|
||||
- Solve the delegated objective inside that sandbox by using the normal Codex tool path and the Android tools that are available on-device.
|
||||
- Ask the Agent a concise free-form question only when you are blocked on missing intent, missing constraints, or a framework-owned action.
|
||||
- Do not assume you can reach the internet directly. Model and auth traffic are Agent-owned.
|
||||
- Do not rely on direct cross-app `bindService(...)` or raw local sockets to reach the Agent. Use the framework-managed session bridge.
|
||||
|
||||
## Shell and device tooling
|
||||
|
||||
- Prefer standard Android shell tools first: `cmd`, `am`, `pm`, `input`, `uiautomator`, `dumpsys`, `wm`, `settings`, `content`, `logcat`.
|
||||
- Do not assume desktop/Linux extras such as `python3`, GNU `date -d`, or other non-stock userland tools are present.
|
||||
- When a command affects app launch or user-visible state, prefer an explicit `--user 0` when the tool supports it.
|
||||
- Keep temporary artifacts in app-private storage such as the current app `files/` or `cache/` directories, or under `$CODEX_HOME`. Do not rely on shared storage.
|
||||
|
||||
## UI inspection and files
|
||||
|
||||
- In self-target Genie mode, prefer `uiautomator dump /proc/self/fd/1` or `uiautomator dump /dev/stdout` when stdout capture is acceptable.
|
||||
- Plain `uiautomator dump` writes to the app-private dump directory.
|
||||
- Explicit shared-storage targets such as `/sdcard/...` are redirected back into app-private storage in self-target mode.
|
||||
- Do not assume `/sdcard` or `/data/local/tmp` are readable or writable from the paired app sandbox.
|
||||
|
||||
## Presentation semantics
|
||||
|
||||
- Detached launch, shown-detached, and attached are different states.
|
||||
- `targetDetached=true` means the target is still detached even if it is visible in a detached or mirrored presentation.
|
||||
- If the task says the app should be visible to the user, do not claim success until the target is attached unless the task explicitly allows detached presentation.
|
||||
- Treat framework session state as the source of truth for presentation state.
|
||||
|
||||
## Working style
|
||||
|
||||
- Prefer solving tasks with normal shell/tool use before reverse-engineering APK contents.
|
||||
- When you need to ask a question, make it specific and short so the Agent can either answer directly or escalate it to the user.
|
||||
""";
|
||||
}
|
||||
|
||||
private static void ensureCodexHome(File codexHome) throws IOException {
|
||||
if (!codexHome.isDirectory() && !codexHome.mkdirs()) {
|
||||
throw new IOException("failed to create codex home at " + codexHome.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.openai.codex.bridge;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import org.junit.Test;
|
||||
|
||||
public final class HostedCodexConfigTest {
|
||||
@Test
|
||||
public void writeInstallsConfigAndAgentsFile() throws Exception {
|
||||
File codexHome = Files.createTempDirectory("hosted-codex-home").toFile();
|
||||
|
||||
HostedCodexConfig.write(codexHome, "http://127.0.0.1:8080");
|
||||
|
||||
String configToml =
|
||||
new String(
|
||||
Files.readAllBytes(new File(codexHome, "config.toml").toPath()),
|
||||
StandardCharsets.UTF_8);
|
||||
String agentsMarkdown =
|
||||
new String(
|
||||
Files.readAllBytes(new File(codexHome, "AGENTS.md").toPath()),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(configToml.contains("model_provider = \"android-openai-http\""));
|
||||
assertTrue(configToml.contains("base_url = \"http://127.0.0.1:8080\""));
|
||||
assertEquals(HostedCodexConfig.buildAgentsMarkdown(), agentsMarkdown);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installAgentsFileWritesExpectedGuidance() throws Exception {
|
||||
File codexHome = Files.createTempDirectory("hosted-codex-agents").toFile();
|
||||
|
||||
HostedCodexConfig.installAgentsFile(codexHome);
|
||||
|
||||
String agentsMarkdown =
|
||||
new String(
|
||||
Files.readAllBytes(new File(codexHome, "AGENTS.md").toPath()),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
assertEquals(HostedCodexConfig.buildAgentsMarkdown(), agentsMarkdown);
|
||||
assertTrue(agentsMarkdown.contains("The user interacts only with the Agent."));
|
||||
assertTrue(
|
||||
agentsMarkdown.contains(
|
||||
"Do not rely on direct cross-app `bindService(...)` or raw local sockets"));
|
||||
assertTrue(
|
||||
agentsMarkdown.contains(
|
||||
"If the task says the app should be visible to the user, do not claim success until the target is attached"));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.app.agent.GenieRequest
|
||||
import android.app.agent.GenieService
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.openai.codex.bridge.HostedCodexConfig
|
||||
import java.io.BufferedWriter
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
@@ -78,6 +79,7 @@ class CodexAppServerHost(
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
}
|
||||
HostedCodexConfig.installAgentsFile(codexHome)
|
||||
val proxy = GenieLocalCodexProxy(
|
||||
sessionId = request.sessionId,
|
||||
socketDirectory = context.cacheDir,
|
||||
|
||||
@@ -28,6 +28,9 @@ The current repo now contains these implementation slices:
|
||||
- The current session bridge exposes small fixed-form calls, and the Genie
|
||||
runtime already uses it to fetch Agent-owned runtime metadata from the
|
||||
hosted Agent Codex runtime, including auth status and configured model/provider.
|
||||
- The Android host layer now installs a generated `AGENTS.md` into the Agent
|
||||
Codex home and every per-session Genie Codex home so the hosted runtimes get
|
||||
the same device-operating guidance at startup.
|
||||
- Target-package planning now relies on the hosted Agent Codex runtime using
|
||||
standard Android shell tools already available on-device (`cmd package`, `pm`,
|
||||
`am`) instead of Kotlin-side app discovery wrappers.
|
||||
|
||||
Reference in New Issue
Block a user