Franky
Franky runs a coding agent inside a fresh, hardened Docker container. It clones a trusted repository, makes a change, and opens a pull request.
franky build https://github.com/you/repo/issues/42
franky build jira FOO-123 --repo you/repo
franky build "add JSON output" --repo you/repo
franky iterate https://github.com/you/repo/pull/42
Franky supports pi, claude, codex, and opencode. The default engine is pi.
Install
Install the franky-agent package. The installed command is franky.
uv tool install franky-agent
# Or:
pipx install franky-agent
# Or:
pip install franky-agent
Docker must be available. Franky pulls public, version-pinned images from GHCR on the first run.
Native AppArmor hosts also need the packaged task profile. Install it after each Franky update.
(
set -e
profile="$(mktemp)"
trap 'rm -f "$profile"' EXIT
franky apparmor-profile > "$profile"
sudo /usr/sbin/apparmor_parser -K -r "$profile"
sudo /usr/bin/install -m 0644 "$profile" /etc/apparmor.d/franky-task
)
Franky detects AppArmor through Docker. Invalid daemon security data stops the run before Franky creates resources.
Configure
Run the setup wizard:
franky config init
It writes ~/.franky/config with mode 0600. Process environment values override file values.
Every build needs:
FRANKY_ALLOWED_REPOS: Comma-separatedowner/repopatterns. An empty value denies every repository.GH_TOKEN: A narrow token with content and pull-request access for the allowed repositories.- Credentials for the selected engine.
JIRA inputs also need JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN.
franky config set FRANKY_ALLOWED_REPOS
franky config set GH_TOKEN
franky config list
franky config path
See config.example.toml for common settings.
Engines
Select an engine with --engine, then FRANKY_ENGINE, or use the default pi.
| Engine | Authentication | Notes |
|---|---|---|
pi |
One supported provider variable | Vendor-neutral BYOK client |
claude |
CLAUDE_CODE_OAUTH_TOKEN |
Claude Code subscription token |
codex |
franky auth login codex or CODEX_API_KEY |
ChatGPT subscription or API key |
opencode |
FRANKY_MODEL plus its matching key |
Moonshot or OpenRouter |
Configure OpenCode for one provider:
franky config set FRANKY_ENGINE opencode
franky config set FRANKY_MODEL moonshotai/kimi-k3
franky config set MOONSHOT_API_KEY
OpenRouter uses FRANKY_MODEL=openrouter/<model-id> with OPENROUTER_API_KEY.
Codex subscription authentication stays in a Docker named volume:
franky auth login codex
franky auth status codex
franky auth logout codex
Set FRANKY_CODEX_AUTH_VOLUME to isolate logins between Franky instances. CODEX_API_KEY takes precedence when both methods exist.
Release runs use an engine-specific image. The unsuffixed image contains all engines for cross-engine tasks.
| Setting | Purpose |
|---|---|
FRANKY_IMAGE |
Override the task image. |
FRANKY_PROXY_IMAGE |
Override the proxy image. |
FRANKY_GHCR_REPO |
Override the GHCR namespace. |
Command catalog
Run franky COMMAND --help for flags and examples. Run franky schema for the complete machine-readable contract.
Main commands
| Command | Purpose |
|---|---|
franky build TASK |
Implement a GitHub issue, JIRA task, prose task, or stdin task. Open one PR. Add --thread to hand the engine session to the PR's author thread. |
franky iterate PR_URL |
Address review or CI feedback with additive commits on Franky's existing PR. Add --thread to continue the PR's author session. |
franky review-pr PR_URL [INSTRUCTIONS] |
Review a PR. Publish findings unless --no-publish is set. Add --thread to continue one stored review session per PR. |
franky plan TASK |
Request a read-only scope assessment and return PR-sized tasks. |
franky jobs |
List runs. Add --stats for success, hang, duration, and cost data. |
franky gh ARGS... |
Run the host gh CLI with Franky's token. |
franky schema |
Print all commands, arguments, flags, result shapes, and exit codes as JSON. |
franky version |
Print version, install source, engine, and selected image. |
franky update |
Install the latest release through the detected installer. |
franky apparmor-profile |
Print the native Linux task profile. |
Job commands
| Command | Purpose |
|---|---|
franky job status JOB_ID |
Show the record, live state, and runtime diagnostics. |
franky job logs JOB_ID |
Print the redacted transcript. |
franky job kill JOB_ID |
Stop a run and remove its task, proxy, network, and volumes. A --thread build also keeps its engine session. |
franky job export JOB_ID |
Export the record and redacted transcript as a portable archive. |
franky job diagnose JOB_ID |
Request a read-only failure analysis. |
franky job replay JOB_ID |
Reproduce a build from saved inputs in a fresh container. |
franky job resume JOB_ID |
Continue a stopped run from its saved workspace, and for a --thread build also its engine session. |
franky job attach JOB_ID |
Send one correction to a running steerable engine. |
replay starts from saved inputs. resume restores /work, plus the engine session when it can (see Author threads). Use replay --open-pr only when the reproduced run should open a PR.
attach supports pi, claude, and codex. OpenCode does not support steering.
Thread commands
| Command | Purpose |
|---|---|
franky threads list |
List stored review and author threads with engine, last head, and session size. |
franky threads prune |
Remove orphaned, idle (--older-than, default 30d), and optionally closed-PR (--closed) threads. Cap stored sessions (--max-bytes, default 2G). |
franky threads purge OWNER/REPO#N |
Delete one PR's threads. --role reviewer or --role author limits it to one role. --all deletes every thread. |
prune --repo OWNER/REPO applies every pass to one repository, so a token scoped to that repository covers --closed. It skips the global disk cap and reports disk_skipped.
Authentication, configuration, and profile commands
| Command | Purpose |
|---|---|
franky auth login codex |
Create persistent Codex subscription authentication. |
franky auth status codex |
Check the stored Codex authentication. |
franky auth logout codex |
Delete the Codex authentication volume. |
franky config init |
Create or update the user configuration. |
franky config set KEY [VALUE] |
Set one configuration value. Secret values use a hidden prompt. |
franky config list |
List configuration values with secrets masked. |
franky config path |
Print the configuration path. |
franky profile init |
Discover setups and merge profile selections. |
franky profile check |
Validate files, MCP policy, limits, and secrets. |
franky profile show |
Print the profile and expanded file list. |
franky profile path |
Print the resolved profile path. |
Build and review behavior
TASK accepts a GitHub issue URL, jira KEY, prose, or - for stdin.
printf '%s\n' 'fix the flaky retry test' | franky build - --repo you/repo
franky build "split this parser" --repo you/repo --plan-first
franky build "fix the flaky retry test" --repo you/repo --retry 2
--plan-first requests a planning pass before the build. Use --yes for non-interactive approval.
Before each build attempt, Franky checks for an open PR on the predicted branch. --force skips this idempotency check.
iterate only targets an allowlisted PR from a same-repository franky/* branch. Its prompt forbids force pushes, new PRs, and merges.
The review-pr prompt tells the agent to inspect only. The host publishes comments or change requests after it rechecks the PR head.
Scope GH_TOKEN permissions because they are the enforced GitHub boundary for the autonomous container.
Use --expected-head-sha to reject a changed PR head. Use --no-publish for a read-only GitHub run.
Review threads
review-pr --thread keeps one review session per PR and role under ~/.franky/threads (override: FRANKY_THREADS_DIR). The next run continues it:
- With
claude, Franky resumes the same session (--resume). Its id is saved before the container starts, so a crash leaves a known id. - Other engines, and a session that is stale, changed, or rejected, start a new session seeded with the stored findings. A thread never blocks the review.
- If a resumed session fails, Franky drops it and retries once in the same run with a seeded session. The result reports
session: seededwithsession_reason: resume_failed. - A new session is stored only after a clean run with parsed findings. A failed run keeps the previous session and findings.
- The prompt tells the agent to verify each prior finding (
openorresolved), review the delta since the last reviewed head, and report new findings on unchanged code only at blocking severity. - A second run for the same PR while one is active exits 4 (
thread_busy). - Thread ids are lowercase, so
Me/Repoandme/reposhare one thread. - Without
--thread, Franky ignores any findingstatusthe agent reports. --rubric-versionpins a rubric label. Changing it, the engine, or the model starts a new session.
Sessions move by stream-in (tar over docker exec) and copy-out (a docker cp ... - tar stream, capped at 64 MiB) only. No volume or mount is added. Only the session file and its side directory move. Claude's project memory never moves between runs. Stored sessions contain regular files only, are scrubbed of known secret values and token patterns, and are refused on any finding. They stay host-only (0700/0600) and are never exported. The stored findings are redacted the same way. threads prune and threads purge delete them.
A resumed session replays earlier content from the same PR to the read-only reviewer. It never crosses PRs or roles. Resume stops after 14 days without a successful review, or above 64 MiB of session files.
Author threads
build --thread and iterate --thread keep one author session per PR (role author). The reviewer and author roles never share a session.
build --threadwithclaudesaves a session id in the job record before the container starts. When the PR exists, the session becomes the PR's author thread (thread.id). The result reportssession_reason: new_thread.- Before the bind, the host confirms that the open PR on the build's branch is the PR the agent reported, and records the session sidecar and PR URL in the job record. If either step fails, the bind stays pending (
bind_pending). - The bind waits at most 60 seconds for a busy author thread. After that it stays pending; the job record keeps the session sidecar and the PR URL. It never overwrites a stored author session (
thread_exists). A session that is not stored is deleted unlessjob resumecan still use it (a workspace snapshot exists). iterate --threadresumes the author session withclaude. Other engines, and a session that is stale, changed, or too large, start a new session seeded with the stored PR head. The prompt fences that context as untrusted data and says that its own conventions override the earlier conversation.- If
iterate --threadfinds no author thread, or one whose bind never finished, it first binds the newest unbound session sidecar of abuild --threadfor that PR whose branch the host confirms. A sidecar that fails to extract or verify is skipped for the next one. This recovers a build that stopped between its record write and the end of its bind. - An author run retries only when the engine refuses the stored session at startup (
resume_failed, one seeded retry). Any other failure never re-runs the pass, because it can already have pushed. - An author session resumes at most 10 times in a row. The next run starts a seeded session (
resume_cap) and resets the count. - A second author run for the same PR while one is active exits 4 (
thread_busy).
A timed-out build --thread copies its session out with its workspace. job kill copies it out before it removes the container, then scrubs it with the loaded configuration's secrets, the profile's MCP credentials, and every secret key in the environment. If the configuration or the profile cannot load, job kill captures no session. Each session goes to a <job_id>.session.tar.gz sidecar beside the run record: regular files only, scrubbed, fail-closed verified, host-only (0600), and never exported. Run-record pruning deletes it with its record.
job resume of such a run restores /work and the session in one container, and resumes the session (V2). Before it streams the session in, it verifies it again, with the profile's MCP credentials too. It falls back to a workspace-only resume (V1) with thread.session: fresh plus the reason: session_missing, session_corrupt, engine_changed, model_changed, no_native_resume, or verify_failed (also when the profile cannot load). The fallback also prints one stderr line, except under --json or --quiet, where the JSON reason carries it. If the engine refuses the restored session at startup, Franky retries once as V1 in a new container with a new session id (resume_failed). A resumed run that opens the PR binds its session like build --thread.
threads purge OWNER/REPO#N --role author deletes only the author thread.
Agent and script interface
franky schema is the discovery entry point for an AI agent. It emits one JSON object with:
- Every live command and subcommand.
- Ordered positional arguments, including requiredness, arity, type, and choices.
- Flags, types, defaults, and help.
- Per-command JSON output mappings and all result shapes.
- Stable exit-code meanings.
Commands that offer --json emit one JSON value on stdout. jobs emits an array unless --stats is set.
Interactive commands fail with exit 2 in a non-TTY. They do not wait forever. Stdin tasks need piped input.
--max-duration SECONDS limits agent runs. Most runs default to 1800 seconds. job diagnose defaults to 300 seconds.
| Exit | Meaning |
|---|---|
0 |
Success, including an existing open PR. |
2 |
Usage error or required interactive input. |
3 |
Invalid configuration. |
4 |
Rejected task or repository. |
5 |
Missing or rejected authentication. |
6 |
Docker, image, or required host tool unavailable. |
7 |
Agent or result failure, including a stale review head. |
8 |
JIRA or network failure, including review publication. |
9 |
Run timeout. |
Every run stores a small JSON record and a redacted transcript under FRANKY_RUNS_DIR. The default is ~/.franky/runs.
franky gh is different from the autonomous container path. It exposes the full host gh surface and ignores FRANKY_ALLOWED_REPOS.
Scope GH_TOKEN to the exact repositories and permissions that callers may use. FRANKY_GH_TIMEOUT limits each host command.
Operator profiles and MCP
Profiles inject selected instructions, skills, prompts, rules, agent definitions, and knowledge files into the task container.
# ~/.franky/profile.toml
[setups]
claude = "~/.claude"
codex = "~/.codex"
Franky uses setup allowlists. It excludes credentials, sessions, plugins, caches, hooks, settings, databases, and binary files.
Each included file passes a fail-closed secret scan. Swept setup configuration never enables MCP automatically.
Explicit MCP configuration can declare validated JSON or TOML files, credential names, and allowed hosts.
franky profile init
franky profile check
franky profile show
franky profile path
See docs/profiles.md for formats, limits, MCP rules, and security behavior.
Resource use
Franky stores /work, /home/franky, and /tmp in disposable disk volumes. Only small runtime paths use tmpfs.
| Resource | Default | Configuration |
|---|---|---|
| Task tree, including nested containers | 2048 MiB RAM, no swap | FRANKY_MEMORY_MB, 256 through 8192 |
| Proxy | 128 MiB RAM, no swap | Fixed per task |
| Task disk data | 8192 MiB soft budget | FRANKY_DISK_MB, 1024 through 32768 |
| Disk helper | 64 MiB RAM | Short-lived and networkless |
For two jobs on a 16 GiB Mac, allocate at least 6 GiB to Docker Desktop. Start with one active job per caller.
Limits are ceilings, not reservations. The disk watchdog samples approximately every five seconds, so it is not a filesystem quota.
Docker shares common image layers across Franky instances. Engine-specific images omit the other engine clients.
The fixed Ubuntu 24.04 footprint gate validates native AppArmor, security, images, and two-job and four-job runtime loads.
make footprint
make smoke-memory
make smoke-memory ARGS="--jobs 4"
Synthetic checks do not prove that an arbitrary repository or live model workload fits. Test your largest repository before increasing concurrency.
Security model
The autonomous engine disables its own approval and sandbox prompts. The container is the safety boundary.
Franky applies these controls:
- A non-root user, read-only root, process and memory limits, and no swap.
- No host bind mounts, host filesystem access, host Docker socket, or privileged container.
- A packaged seccomp policy and the named
franky-taskAppArmor profile on native hosts. - A fail-closed repository allowlist.
- Name-only credential forwarding and redaction of autonomous output and stored transcripts.
- An internal Docker network with no direct internet route or task DNS.
- A default-deny, HTTPS-only Squid proxy with blind TLS tunnels.
- Prompts forbid autonomous merges. Token permissions enforce the available GitHub actions.
Each task runs its own rootless Docker daemon. Agents can build images, run Compose, and use testcontainers without reaching the host daemon.
task and nested Docker -> internal network -> Squid proxy -> allowed HTTPS hosts
The default egress list includes the selected provider, GitHub, package registries, and container registries. Add required hosts with FRANKY_EXTRA_ALLOWED_DOMAINS.
Allowlisted hosts remain trusted destinations, not inert endpoints. A hostile task can use available credentials against those hosts.
The agent can also pass its credentials to nested containers. The outer limits and egress policy still apply to the full task tree.
Opening a PR can start GitHub Actions. Review workflow changes before you allow a run to use repository secrets.
Codex subscription authentication is the only persistent task volume. Franky scrubs it to auth.json before each autonomous run.
Read AGENTS.md before changing security, container, egress, profile, or resource code.
Development and evaluation
AGENTS.md is the canonical maintainer guide. CLAUDE.md links to it for Claude Code.
The opt-in eval harness measures pass rate with real Docker, credentials, and a throwaway repository:
make eval ARGS="-n 3 --engine pi --compare-engine codex"
See evals/README.md for task formats. See docs/releasing.md for releases.
Status
v0.3.0. Live end-to-end runs require operator-supplied credentials.
Release files for franky-agent 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| franky_agent-0.3.0.tar.gz | 363.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| franky_agent-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 581.4 kB
Release files / franky_agent-0.3.0.tar.gz
| Download URL | franky_agent-0.3.0.tar.gz |
|---|---|
| Size | 363.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb40fdf9631ea8a21f63e92ba192a49eb13cbebeee96590d16148e099ff3c69a
|
|
BLAKE2b-256 checksum How to use checksums |
b83b991c1d0c1e60480cfd1de9f548fe522938c3c5ff7b0f74c2834f5a09a1d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / franky_agent-0.3.0-py3-none-any.whl
| Download URL | franky_agent-0.3.0-py3-none-any.whl |
|---|---|
| Size | 218.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
248b5dc614f263fb2d48c7a85b4cda0e1824400879bbf6d6d6b28681ba5eff34
|
|
BLAKE2b-256 checksum How to use checksums |
7691107e78c3e54722e3979bca82c33b7cf8ae9fa13f8e2e704bc217062286e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log