flutter_agent_harness 0.1.295
flutter_agent_harness: ^0.1.295 copied to clipboard
Cross-platform AI agent harness for Dart and Flutter: streaming provider adapters, agent loop with tools, session persistence, context compaction.
flutter_agent_harness #
Cross-platform AI agent harness for Dart and Flutter — streaming provider
adapters, an agent loop with native tool calling, JSONL session persistence,
and context compaction. Architecture ported from
pi-mono (packages/ai +
packages/agent), with a pure-Dart core that runs on the VM, Flutter
desktop/mobile, and web.
Status: early development (Phase 0). See GOAL.md for the roadmap, quality gates, and design contract. The API is not yet stable.
fa1.dev — the project website: a live in-browser demo of the full agent (sandboxed shell, git, interpreters — your key stays in page memory), the iOS public beta on TestFlight, the macOS app, and the CLI installer below.
Highlights (target design) #
- Streaming-first:
Stream<AgentEvent>from every provider, partial-first deltas — each event carries the current partial message. - Errors-as-events: providers never throw; failures arrive as
errorevents with astopReason, so the agent loop never dies on a 429 or a dropped connection. - Native tool calling (OpenAI tools, Anthropic tool_use, Google functionCalling) — no prompt-based JSON scraping.
- Token-based context management: inline usage accounting, overflow detection, LLM-powered compaction.
- Sessions as append-only JSONL trees behind a storage abstraction — portable to web and mobile.
- Cancellation everywhere via
CancelToken.
Usage (current seed) #
import 'package:flutter_agent_harness/flutter_agent_harness.dart';
void main() async {
final source = CancelTokenSource();
// Pass source.token into any long-running operation.
final cancelled = source.token.onCancel.then((_) => print('aborted'));
source.cancel('user pressed stop');
await cancelled;
}
Provider adapters and the agent loop land in the next phases — see the roadmap in GOAL.md.
Install #
curl -fsSL "https://fa1.dev/install.sh" | sh # macOS / Linux / WSL
dart pub global activate flutter_agent_harness # fa + fah on your PATH
The installer detects the OS/architecture, downloads a prebuilt binary from the latest GitHub Release, puts it on your PATH, and (on macOS) strips Gatekeeper quarantine and re-signs it. More install paths — the web demo, the Flutter app — live on fa1.dev.
CLI (fah) #
A pi-like terminal coding agent ships in bin/fah.dart: a REPL with the
built-in read / write / ls / bash tools, JSONL session persistence
under ~/.fah/sessions (cwd-encoded layout), automatic context compaction,
and slash commands.
export OPENROUTER_API_KEY=sk-or-... # or ANTHROPIC_API_KEY / GOOGLE_API_KEY
dart run bin/fah.dart # defaults: OpenRouter, claude-sonnet-4
dart run bin/fah.dart --provider anthropic --model claude-sonnet-4-5
dart run bin/fah.dart --model openai/gpt-4o-mini --cwd . --session-root /tmp/fah
Headless mode runs a single non-interactive prompt and exits — the response
streams to stdout, tool indicators and notices go to stderr (stdout stays
pipeable), nothing is ever prompted interactively, and the session persists
like a REPL run. Exit codes: 0 ok, 1 provider error, 130 aborted (Ctrl-C).
A first positional naming an existing file becomes the prompt source: text
files (.md, .markdown, .txt) are inlined as the prompt; any other
(binary) file is attached as a path reference for the agent's tools — in
both cases trailing text appends as the instruction. A path that does not
exist is treated as plain prompt text.
dart run bin/fah.dart "summarize the changelog" # positional prompt
dart run bin/fah.dart -p "fix the typos in README.md" # -p/--prompt alias
dart run bin/fah.dart CHANGELOG.md "summarize this" # text file as prompt
dart run bin/fah.dart screenshot.png "describe it" # binary → path reference
dart run bin/fah.dart "summarize the changelog" | pbcopy # pipes cleanly
Flags: --model <id>,
--provider openai-completions|anthropic|google|dial|minimax|zai,
--base-url <url>, --cwd <dir>, --session-root <dir>, -p/--prompt <text>, --help, --version.
The chatgpt provider (Codex backend) is also available: sign in with a
ChatGPT account via /provider chatgpt oauth in the REPL (OAuth-only —
there is no headless --provider chatgpt flag). Several ChatGPT accounts
can coexist: the flow offers the saved accounts first, each account keeps
its own named entry and secure-store slot, and re-auth never touches a
sibling account's credentials.
Env preconfig (Docker / headless) #
FA_PROVIDER_TYPE + FA_PROVIDER_CONFIG boot a declared provider with
no saved config, and the declaration becomes the session default for
every model role (default/smol/slow/plan) — the same selection a
/provider <name> switch makes:
FA_PROVIDER_TYPE=zai
FA_PROVIDER_CONFIG='{"baseUrl":"https://api.z.ai/api/coding/paas/v4","model":"glm-5.3","apiKeyEnvVar":"ZAI_API_KEY"}'
ZAI_API_KEY=sk-...
baseUrl and model are required — no catalog defaults fill gaps; a
missing field fails loud at boot. apiKeyEnvVar is optional: declared,
the named env var (or its _BASE64 twin) must hold the key; omitted,
the provider boots keyless and the spec's usual env names are never
probed. Every text value has a base64 twin for CI platforms that mangle
special characters — FA_PROVIDER_CONFIG_BASE64, and
<apiKeyEnvVar>_BASE64 for the key: the plain value wins when both
carry the same value; mismatched or malformed twins fail loud.
# base64 twin form (identical boot):
FA_PROVIDER_CONFIG_BASE64=$(printf '%s' "$FA_PROVIDER_CONFIG" | base64)
ZAI_API_KEY_BASE64=$(printf '%s' "$ZAI_API_KEY" | base64)
GitHub Copilot is a first-class provider. /provider copilot connects a
GitHub account via the device-code flow (open the shown
verification_uri, enter the user_code) or by pasting an existing PAT
— the flow works headless too — and the Flutter app offers the same
connect as a sheet. Accounts save as named entries (copilot-<login> by
default); the plan picks the host — individual api.githubcopilot.com,
business api.business.githubcopilot.com, enterprise
api.enterprise.githubcopilot.com, or a custom --base-url override —
and several accounts can coexist side by side. Tokens live only in the
OS secure store (Keychain / Secret Service); config.yaml carries name,
plan, and baseUrl, never a token. CI runs store-less via the
FA_KEY_COPILOT_<NAME> env (plus a _2… ring for more entries), and
headless runs take --provider copilot --model <id> with
COPILOT_GITHUB_TOKEN. Models come from a live GET /models (with
capabilities and limits). The device-flow client id is overridable via
FA_COPILOT_CLIENT_ID — that GitHub endpoint is undocumented, so a
custom client id carries an account-ban risk; override only with cause.
Slash commands inside the REPL: /exit, /reset (new session), /compact
(summarize history now), /stats (token/cost totals), /model <id> (show or
switch model), /approval [always-ask|write|yolo] (tool approval mode),
/allow [tool] (always-allow a tool), /help. While a run is streaming,
typed input is steered into the agent; Ctrl-C aborts the run (Ctrl-C at the
idle prompt exits).
Tool calls pass an approval gate (lib/src/approval/): every tool has a
capability tier (read/write/exec; exec for undeclared custom tools), the
session mode decides what runs unattended, and per-tool overrides plus a
critical-pattern interceptor for bash (e.g. rm -rf /, fork bombs,
curl … | sh) can force a prompt — even in yolo. The prompt UI is an
injectable callback; piped (non-interactive) input denies prompt-policy
calls with a reason. Mode and always-allowed tools persist in
~/.fah/config.yaml.
The CLI core (AgentCli + CliIO) is pure Dart and lives in
lib/src/cli/agent_cli.dart; only bin/fah.dart and lib/io.dart touch
dart:io.
Agent-to-agent messaging (DAP) #
The CLI ships a default-on DAP/1 hub plugin (bin/fah_hub_plugin.dart +
the hosted fah_hub_client pub package): agents connect to a hub over a signed
WebSocket, exchange end-to-end encrypted channel messages and DMs (the
hub only ever sees ciphertext), and see each other's presence. Inbound
hub mail is drained into the agent loop as steering messages; /dap and
the dap_* tools drive the connection. See docs/dap.md
for the protocol, the hub server, and an end-to-end setup walkthrough.
Development #
dart pub get
dart test --coverage=coverage --exclude-tags integration
dart run coverage:format_coverage --lcov -i coverage -o coverage/lcov.info
python3 scripts/check_coverage.py
Pre-commit hook (analyze + tests + coverage ≥ 80% + duplication < 1%):
cp scripts/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
License #
MIT — see LICENSE.