Ottto Local Platform
This is the clean-slate local runtime for customer machines. It is the target
production path for the daemon, CLI, macOS app, agent skill, and /apps
integration. The retired Python companion implementation has been removed from
the production repository path.
Public V1 Promise
The public ottto repository is intended to be enough for an external
developer, support engineer, CI job, or coding agent to install, set up, verify,
repair, diagnose, update, and uninstall Ottto without private repository
knowledge.
Stable releases are distributed through the install owners advertised by the release manifest:
- Homebrew tap: installs
ottto, installsottto-service, and ownsbrew services start ottto. - Hosted native installer: downloads and verifies a signed, notarized DMG or PKG without installing mutable shell-managed payloads.
- GitHub Releases and release notes: publish immutable manifest-backed artifacts, checksums, SBOM/provenance metadata, and rollback instructions.
- Signed
Ottto.app: provides the native app path while staying aligned with the sameottto-serviceruntime and release manifest.
net.ottto.service is a single-owner user LaunchAgent. A normal install,
launch, upgrade, or restart may repair only the LaunchAgent it already owns:
Homebrew through brew services, the native app through its bundled helper,
and the hosted installer through its installed helper. Replacing a different
owner requires an explicit migration command; ordinary app launch must use a
Homebrew-owned service without rewriting it, and ordinary Homebrew service
start must not silently take over an app-bundle-owned job.
Final public-v1 closeout verifies that the public repository resolves as
ottto-ai/ottto, uses main as its default branch, and has that public main
branch head bound to the launch runtime commit. It also verifies that the exact
stable GitHub Release is non-draft, non-prerelease, has targetCommitish
bound to either main or the launch runtime commit, and contains only
canonical tag-scoped download assets for release-manifest.json, every stable
manifest artifact, and the CycloneDX SBOM before
stable_release_published can pass.
The same closeout requires a redacted public Public CI push-run report from
the public runtime commit whose public run URL matches the reported run ID,
with a unique workflow job inventory, canonical public GitHub job URLs for that
run, and the reviewed runtime, public-surface, and advisory jobs green.
The public repo, release, CI, and final closeout checked_at fields plus
GitHub release/CI timestamps must be parseable ISO-8601 UTC timestamps, so
reviewed evidence cannot carry ambiguous local time strings. Release and CI
access reports also require checked_at to be at or after the reported GitHub
event, and final closeout requires its checked_at to be at or after each
component access report.
Stable preflight also requires a passing, redacted internal stable-candidate RC
evidence record from a stable-candidate manifest built from the same commit,
so stable publication cannot skip candidate artifact trust, installer, setup,
diagnostics, update, rollback, and static install-owner checks. The
stable-candidate RC gate rejects candidate macOS artifacts that are not marked
signed, notarized, and Gatekeeper-assessed, and its evidence skeleton requires
explicit signature, notarization, Gatekeeper, and local-runtime pass/fail facts
bound to ottto-service, net.ottto.service, protocol v15, the
stable-candidate version/channel, and candidate release-manifest SHA-256.
Stable closeout then requires clean-machine evidence for each advertised install
owner, including setup, app detection, Codex verify/fix, diagnostics, logout,
update/upgrade, uninstall, reinstall, post-reinstall status, re-onboard after a
local Keychain wipe, and owner-specific trust checks before latest can be
treated as externally ready. Each owner entry must also bind the installed local
runtime to ottto-service,
net.ottto.service, protocol v15, the exact stable version/channel, install
owner, and release-manifest SHA-256. The closeout gate rejects extra required
install owners and unknown per-owner check names so the matrix cannot pass with
unreviewed launch evidence.
Setup is browser-claim-first. The CLI never collects an Ottto password in the
terminal. Agents and headless sessions use --json --no-browser --no-wait,
show the returned claim URL or claim code to the user, and resume through the
same daemon-owned setup flow.
The first public smoke commands are:
ottto status --json
ottto receipts --json --limit 20
ottto context --json
ottto setup --json
ottto apps detect --json
ottto verify --app codex --json
ottto verify --repair --app codex --json
ottto doctor --json
ottto fix --app codex --json
ottto diagnostics collect --json
ottto update check --json
Privacy defaults are part of the product contract. Local usage sync starts only
after claim approval, uses a six-month default backfill capped at 1000 files per
app/source, and must not upload raw prompts, raw output, absolute local paths,
cookies, credentials, or secret material. Live telemetry is source-level
opt-in, diagnostics upload is explicit-approval-only, and risky collectors are
excluded from default v1. Codex Cloud Sessions additionally requires the
user's versioned grant for the exact local device/user/organization scope plus
server policy approval; without both, its supervisor remains inert.
OTTTO_CODEX_CLOUD_SESSIONS_DISABLED=1 always wins.
This public repository owns the local runtime, installer/release tooling, public docs, tests, redaction, diagnostics, agent adapters, connector SDK/testkit, manifest schemas, and safe first-party source packages. The cloud application, backend, frontend, billing, auth, analytics, infrastructure, private operations, and planning docs remain private.
Crates
ottto-protocol: stable JSON models shared by the daemon, CLI, app, agent skill, backend event ingestion, and web setup flows.ottto-core: platform-neutral local logic such as source health summaries, repair planning primitives, diagnostics redaction, and stable CLI errors.ottto-service: the per-user daemon and future local authenticated control API owner.ottto-cli: theotttodeveloper, support, CI, and AI-agent interface.
Contract First
Every customer-facing local action should cross the typed protocol boundary:
- status
- receipts
- setup
- doctor
- fix
- verify
- diagnostics collect
- update check
- uninstall
The SwiftUI app and agent skill should talk to ottto-service through this
protocol. The app must not shell out to the CLI for normal behavior, and the web
app must not duplicate local setup logic.
Public Docs
External install, setup, privacy, diagnostics, support, connector,
release-verification, agent-adapter, troubleshooting, and JSON automation examples live in
docs/. These docs are written to stand alone in a public
repository or release package without private repo links.
Public Export Gate
The future public ottto repository must be populated from the approved export
surface under this local-platform tree, not by copying arbitrary private
monorepo paths. The export manifest and checks live in
public-export/. Run
scripts/public_repo_export_check.sh --require-no-rewrites before any public
repo bootstrap or runtime move; it verifies export roots, fails private paths
and secret-like content, and fails private repo references that would need to be
rewritten to the public repository name. Current release attestation defaults already name
ottto-ai/ottto, so the expected pre-bootstrap bundle output is
rewrite-required references: 0; any new rewrite-required release/runtime
reference is a regression to fix before cutover.
scripts/public_repo_export_bundle.sh --force
materializes the root-shaped public staging tree, including connector
SDK/testkit crates, first-party source packages, the generated connector
registry, public manifest schemas, public GitHub Actions and Dependabot
configuration, and root Apache-2.0 license, NOTICE, security, contribution,
conduct, support, and trademark policy files. The bundle refuses rewrite-required
source by default; --allow-rewrites is reserved for legacy/test fixtures. The generated manifest records
every staged non-manifest file with SHA-256, size, mode, and executable-bit
metadata plus a deterministic content_sha256; run
scripts/public_repo_manifest_check.sh --staged-output dist/public-export/ottto
to verify that inventory before bootstrap. This repository does not commit
PUBLIC_EXPORT_MANIFEST.json: scripts/public_repo_manifest.sh --digest (or
--facts) derives the same digest and counts from a commit's git tree, and the
public-surface gate requires the self-exported bundle's manifest to equal the
one derived from HEAD. The generated tree must also pass
scripts/public_repo_skeleton_check.sh, which verifies the public repo shape
for runtime crates, connectors, docs, release tooling, policies, CI, and agent
adapters before bootstrap, and
scripts/public_repo_contract_check.sh --staged-output dist/public-export/ottto,
which verifies the generated root-shaped tree still carries the public CLI
JSON/NDJSON fixtures, local-control protocol fixtures, generated connector
registry, manifest schemas, setup claim fixtures, diagnostics redaction
fixture, release manifest schema, and private backend/frontend consumer
assumptions. When --private-repo-root is supplied, the same gate also checks
the private runtime pin at
backend/app/domain/local_platform/public_runtime_pin.json, so private
consumers only validate against the pinned public manifest digest. Once the
public repo is the runtime authority, run the same contract gate with
--require-public-authority; it requires the private pin to name a
public_repo_commit and verifies that the checked public root is a clean git
checkout at that commit. Without --staged-output, the gate derives the public
manifest digest and counts from the checked root's HEAD. Before any public
commit or runtime move, run
scripts/public_repo_secret_scan.sh --staged-output dist/public-export/ottto
to scan current export candidates, candidate path history, and the generated
bundle for private paths and secret-like content.
Run scripts/public_repo_surface_ci.sh --staged-output dist/public-export/ottto
to copy the generated root-shaped bundle into a temporary git checkout and run
the same public-surface gate that the exported GitHub Actions workflow uses:
schema/registry validation, shellcheck, export tests, manifest/skeleton,
secret-scan, contract checks, release/installer dry-run tests, and a
self-exported bundle verification. This includes release manifest,
stable-candidate RC, stable preflight, stable closeout, stable QA template,
Homebrew, hosted installer, and CycloneDX SBOM generator tests.
The default --scope all runs every step and needs macOS. GitHub CI splits the
gate: --scope portable runs on Linux, and --scope macos runs the steps that
need real macOS tools or macOS /bin/bash 3.2 inside the Rust contracts job.
The skeleton gate also requires docs/support.md, the public support runbook
covering redacted triage, diagnostics, escalation, data boundaries, and
public-v1 closeout readiness.
Use scripts/public_repo_bootstrap_plan.sh --target-dir <clean-public-checkout> --report public-bootstrap-plan.json
to build and verify the bundle, check that the target checkout is a clean git
root outside the private monorepo with origin bound to ottto-ai/ottto,
print a dry-run add/change/delete plan, and write a machine-readable report
without absolute local filesystem paths. Only pass --apply after reviewing
that plan; apply mode copies the verified bundle into the target checkout and
reruns the public manifest, skeleton, secret-scan, and contract gates against
the applied tree.
After apply, run
scripts/public_repo_cutover_closeout.sh --target-dir <public-checkout> --source-repo-root <private-monorepo> --bootstrap-report public-bootstrap-apply.json --report public-cutover-closeout.json
before committing or enabling branch protection in the public checkout. The
closeout gate verifies the target manifest, skeleton, secret scan, public/private
contracts, and public-surface CI smoke against the applied checkout, then writes
a path-safe JSON report with the target origin repository binding, git status,
manifest digest, bootstrap report branch/head plus remote/digest consistency,
private source history/consumer-contract scope, per-check results, and
ready_to_commit=true.
CLI Contract
The public CLI is the developer, support, CI, and AI-agent surface over
ottto-service. Help text for visible commands and options is frozen by
fixtures/cli/help-contract.txt and the ottto-cli unit test
cli_help_matches_frozen_contract; update both intentionally when changing the
public command surface.
Codex lifecycle automation uses .agents/skills/ottto/SKILL.md, which stays
thin over this contract: it calls ottto --json, uses public --app commands,
and does not duplicate setup, repair, diagnostics, or install-owner logic.
Claude Code lifecycle automation uses .claude/skills/ottto/SKILL.md, the
project skill equivalent, with no hooks, status-line monitor ownership, or
preapproved tool grants in v1.
MCP is deliberately not a public-v1 adapter. docs/agent-adapters.md records
the deferral: any future MCP server must wrap the stable CLI/local protocol and
must not own setup, repair, diagnostics upload, update, credential, or source
collection authority.
Current locked commands:
ottto status --json
ottto status --json --watch
ottto receipts --json
ottto receipts --json --limit 20 --since 2026-09-10T00:00:00Z --source codex
ottto provider-requests --json
ottto provider-requests --json --since 24h
ottto status --refresh-agent-status --json
ottto context --json
ottto context --json --range today --source codex
ottto context --json --all-machines --max-tokens 4000
ottto costs --json
ottto costs --json --range today --source codex
ottto costs --json --all-machines --bucket day
ottto cloud materials --source vertex --json
ottto cloud plan --source vertex --config-file connector.json --json
ottto cloud register --source vertex --config-file connector.json --approve --json
ottto cloud test --source vertex --config-file connector.json --json
ottto cloud status --source vertex --json
ottto cloud sync --source vertex --days 30 --approve --json
ottto sessions --json
ottto sessions --json --limit 20 --source codex
ottto sessions --json --all-machines --sort-by cost --sort-dir desc
ottto recommendations --json
ottto provider-impact --json
ottto provider-impact --json --provider openai --app codex --kind quota --limit 25
ottto apps --json
ottto apps detect --json
ottto apps status --app codex --json
ottto setup --json
ottto setup --json --no-browser --no-wait
ottto setup --json --timeout 300
ottto setup --claim-code <code> --json
ottto login --json
ottto login --json --no-browser --no-wait
ottto account --json
ottto logout --json
ottto logout --local-only --json
ottto logout --forget-installation --confirm --json
ottto logout --local-only --forget-installation --confirm --json
ottto doctor --json
ottto fix --app codex --json
ottto verify --app codex --json
ottto verify --repair --app codex --json
ottto diagnostics collect --json
ottto diagnostics collect --upload --approve-upload --accept-retention-disclosure --support-claim <claim> --json
ottto update --json
ottto update check --json
ottto uninstall --backup-state ./ottto-state-backup --confirm --json
Automation should pass --json; JSON mode prints one final JSON object and no
human summary text. --json --watch prints newline-delimited JSON progress
events followed by one final event with ok, exit_code, and either payload
or error; setup can return a payload with a nonzero exit_code when it needs
user action or times out. Setup opens a browser claim by default when no local
setup-run binding exists, prints the URL/code fallback in human mode, and waits
up to --timeout seconds for browser approval and setup progress before
returning exit code 61. Agents and headless flows can pass --no-browser to
skip auto-open and --no-wait to return the fallback claim payload immediately
with exit code 60. --watch without --json exits as invalid_request.
Human mode prints short summaries only and is not a parsing contract. context,
costs, sessions, recommendations, and provider-impact are agent-only
JSON commands; omitting --json exits as invalid_request.
All commands call ottto-service through the local-control protocol except
local uninstall cleanup and the hidden Claude Code status-line helper.
Stable CLI exit codes are:
| Code | Meaning |
|---|---|
0 |
Success or setup complete |
2 |
Invalid request or arguments |
10 |
ottto-service unavailable |
14 |
Backend unreachable |
16 |
Backend unavailable |
50 |
Permission denied |
60 |
Setup needs user or browser action |
61 |
Setup timed out |
70 |
Internal error |
Public command nouns use apps and --app for local app lifecycle work. The
lower-level agent-status --source <source> selector remains available for
existing automation while protocol payloads continue to use SourceKind.
context --source <source> intentionally accepts backend source slugs, not only
local app values, because cloud context can include API and cloud-provider
sources in addition to Codex, Claude Code, and Pi.
Current Phase
This workspace contains the Phase 1 protocol/core foundation and the first Phase 2 daemon control primitives:
- authenticated local daemon status access
- daemon-owned relay and source state
- daemon-owned repair locks
- read-only Codex and Claude Code config drift verification with
sha256:fingerprints plusverify --repairWriteConfig repair - repair-plan proposal skeletons with required approval-boundary metadata: setup-safe config repairs may use terminal approval only when tied to an active setup-run binding, credential/auth-adjacent actions require browser approval, disconnected or stale local account state is browser-only, and old repair payloads without authority/approval metadata fail deserialization
- typed diagnostics bundles with explicit redaction reports
- typed local control request/response envelopes
- authenticated
claude_accounts_status,claude_account_prepare,claude_account_reconnect,claude_account_check,claude_account_stop_waiting,claude_account_set_upkeep_consent,claude_account_register_path, andclaude_account_removeoperations for one bounded, versioned machine-local registry. The true defaultCLAUDE_CONFIG_DIR-unset slot is always present; up to nine absolute config-dir strings are stored exactly as registered with stable opaque ids and explicitmanaged/externalownership, and one explicit consent bit governs future background upkeep for the whole registry. Advanced path registration is external ownership. Managed prepare creates a private0700directory and returns an exact Claude Code launch command; customer-owned/loginremains entirely inside official Claude Code.claude_account_prepareremains the managed first-connect operation; exact reconnect reuses one already-registered custom slot and its exact launch command without creating a directory or duplicate registration. Exact checks are single-flight, stop is durable until explicit prepare/reconnect replay, and these protocol-v18 operations perform no login automation, token refresh, inference, credential write, or Keychain write. Authenticated local status inclu
