By default AgentFeedback is one binary and one database owned by one OS user on one machine, with nothing listening (The client on a machine). The optional server is a single-trust-domain service for one team's machines: it is not multi-tenant and is not safe to expose directly to the internet.
- One shared API key authorizes every write, every read and every processed mark. No per-user permissions, no revocation of a single client, no overlap window on rotation (procedure in operate.md).
- HTTP is plaintext. The compose stacks bind
127.0.0.1:8090. SetAGENTFEEDBACK_BIND_ADDRESS=0.0.0.0only inside a trusted private network or behind a reverse proxy that terminates TLS and authenticates. /health,/readyand/metricsneed no key; keep them inside the deployment boundary./skill,/.well-known/agentfeedback.json,/api/v1/openapi.jsonand/api/v1/schemasneed no key and carry no data./mcptakes the same key as/api/v1/*; its DNS-rebinding check is off because the key stands in for it. Turning that check off (DisableLocalhostProtectionin the MCP SDK) is acceptable only on a handler behind the API key; a keyless loopback listener keeps it, or validatesHostandOriginitself asagentfeedback uidoes./mcp/{project}is a convenience, not an access boundary:get_submissionandmark_processedtake ids and are not scoped by the preset. There is no rate limiting; the only server-side bounds are the body caps: 10 MiB for a create or a mark, 32 MiB for an import, and 10 MiB plus 64 KiB for the JSON-RPC message on/mcp.- Keep
infra/agentfeedback/.env, the remote.env, backups and every producer'sAGENT_FEEDBACK_API_KEYprivate..envand.private/are gitignored; never force-add them. - A local-mode write (no
urlconfigured) goes to the data-directory database in-process and never presents the server key: the key guards the network, and the one OS user who can read the database file can already write it. Local mode is for that one user; sharing a machine's queue with others means runningservewith a key. The file inbox (inbox/in the data directory,0700) follows the same rule: a file there is filed by the next command, so only that user may write it; the client never follows a symlink out of it and never reads a file over the create limit. agentfeedback uiserves read-only queue and local session-status pages on a loopback address only (--addrrefuses any other). It is its own handler, never the API or the MCP handler: no/api/v1or/mcproute is reachable through it, and every method butGETandHEADis refused. A request whoseHostis not the bound address (orlocalhoston the same port) is refused, so a web page that rebinds its own DNS name to127.0.0.1reads nothing; so is a request with any foreignOrigin, and no CORS header is ever sent. The path carries a random token made at each launch, so another local process or page that guesses the port still gets404, and no refused request's response carries the token; the URL is printed once, and anyone who has it can read the queue and local session store paths, problems, counts and saved selection until the process exits. Every response isCache-Control: no-storewith a Content Security Policy that allows no remote source, stored text is HTML-escaped, and the page loads no external asset. With--serverthe queue pages read that server with the configured key, which stays in the process. The sessions page reads local status only, without server requests or digest content.
The agentfeedback binary runs when a person, a harness hook or an agent
starts it, or as a bounded upload child of one of those commands:
- No self-update, no resident daemon, no scheduled task, no telemetry.
The automatic session sweep can launch one
detached, short-lived upload child from an existing turn-end hook or CLI
command, bounded by
sessions.push_budgetandsessions.push_batch. It is never resident or scheduled. Onlyserveandui(loopback only) open a listening socket, and both stay in the foreground. Upgrading is installing a new binary. - Network: the client talks to one host, the server
urlit is configured with, and in local mode to none. Explicit onboarding andlogincontact the named server for discovery and authentication.agentfeedback migratealso talks to the target named by--to(cloudishttps://api.agentfeedback.dev) for the duration of that command; its--dry-runsends no record but still reads the target's metadata and tombstones. StandardHTTPS_PROXYvariables apply; redirects are not followed.agentfeedback mcpin remote mode holds the key in memory for the life of the process and sends it, in the same two headers, only to the configured server's/mcp; no MCP entryinstallwrites holds the key or a reference to it (the Codex block namesAGENT_FEEDBACK_API_KEYinenv_vars, so Codex passes the variable through, never its value).skills/agentfeedback/scripts/install.shdownloads the release and itsSHA256SUMSfrom GitHub Releases over HTTPS only, and runsversionon anagentfeedbackit finds already installed to decide whether to keep it. The triage skill's optional clustering helper is a separate boundary (below). - No process enumeration, no keychain or credential store, no clipboard, no
shell history. Commands it runs:
git(read-only, for the context), harness CLIs duringinstall(such asclaude mcp add),agentfeedback version --jsonof the binary each hook names (doctor), andagentfeedback sessions push --due --jsonfor the automatic sweep.
Server onboarding accepts exactly one of an existing key on stdin, email
device login, agent self-enrolment, or an enrolment token on stdin. Only
capability-advertised POST routes are used for the hosted methods; OSS
supports paste only. The human types secrets or completes email confirmation,
not the agent. Keys and tokens never belong in argv, chat, shell tracing or
logs. Device codes and verification URLs are not printed. Agent enrolment's
claim URL is shown once with its deadline and reduced-attribution notice and
must not be logged. No browser session, redirects or dummy credentials are
used. login explicitly reauthenticates a configured machine through email;
neither onboarding nor login silently replaces an existing destination.
Returned keys use the existing atomic 0600 config writer. Hosted credentials
are saved before harness prompts or wiring; a later setup refusal does not
discard the key. An environment API key must be unset before hosted
authentication. Login refuses a different saved destination, including one
changed while email approval was pending.
init, doctor and sessions status also read
GET /api/v1/sessions/policy with the configured key. This policy request
returns the org identifier, processing permission, notice version and upload
bounds, with optional org name and retention; it sends no transcript.
The response is cached in capture_policy in the data-directory database
in remote mode. Local mode makes no request. Discovery alone uploads nothing,
and failures never grant permission. See
Session policy settings for cache and
backoff behavior.
sessions push sends eligible Claude Code, Codex and OpenCode (V1 and 2) transcripts
to that configured server when its organisation enables processing. These
contain prompts, responses, tool calls and tool output. Collection rules,
repository upload vetoes and the policy's consent cutoff apply. Every scrub
class is applied before hashing and partitioning, with JSON strings decoded
so escaping cannot hide credentials and multiline private keys redacted
across records. Logs contain scrub counts, never transcript text. Scrubbing
is not a guarantee that all sensitive prose is recognised. Uploads can be
manual or triggered by the automatic sweep,
which requires matching cached consent (including recovery from transient policy
errors) and refreshes permission in the upload child. See Push for timing, revision, size and retry rules. A dry-run
preview stays in command output; it is not saved or sent to the upload
routes. Pending uploads retain only their initial scrubbed manifest metadata
and partition size in SQLite, never transcript content or parts. Source
permissions and snapshot stability are checked again before each request.
Where it reads and writes (XDG_* unset or not absolute means the default
under the home directory, on every operating system):
| Location | Access | What |
|---|---|---|
${XDG_DATA_HOME:-~/.local/share}/agentfeedback/ |
owned, 0700, files 0600 |
the database (reports in local mode; session watermarks and policy cache in both modes; remote init, doctor and sessions status create it for policy discovery; when switching to a server, existing reports leave the machine only through migrate or init's copy after a yes), spool/, rejected/, inbox/ (operate.md) |
${XDG_CACHE_HOME:-~/.cache}/agentfeedback/ |
owned, 0700, files 0600 |
log/client.jsonl, the hooks' per-session counters (sessions/, deleted after 7 days) and last-run records (hooks/), filed/ markers, digest/ output; push.lock, push.last-started and push.last-run.json coordinate and summarize automatic session sweeps without transcript content |
${XDG_CONFIG_HOME:-~/.config}/agentfeedback/ |
owned, 0700, files 0600 |
config.toml, the install manifest install.json, and server/ (api-key, serve.env) from serve --init |
the working tree's .git metadata and .agentfeedback.toml |
read | the project context and the repository's narrowing rules |
| harness session stores | read, never written | below |
| harness skill, hook, MCP and global instruction files | written by install and uninstall only |
below |
a repository's AGENTS.md or CLAUDE.md |
written by install --project only |
below |
| paths named on the command line | as the command says | backup <dest.db>, import <file>, digest --out, submit review <run_dir> (reads it, writes its .submitted marker), server install (one service file) |
Owner-only modes are not set or checked on Windows. agentfeedback doctor
reports a data-directory path other users can reach, with the chmod to
run.
Session stores. The harness session stores (sessions.md lists
them) are read only by agentfeedback sessions, the UI sessions page,
submit --context-from, the stdio sessions_* tools and the stdio
submit_feedback's context_from (in remote mode too: the session is read
here, and only the body built from it is sent), on demand and never written;
the OpenCode (opencode-v1 and opencode-v2)
database is opened read-only, without creating any file beside it. The [collect]
rules are applied per session before a log is opened, and a digest is
bounded and scrubbed before it is shown. Only watermark and push-outcome
metadata and what you file are stored in the data-directory database;
transcripts are sent only by sessions push (manual or automatic), never spooled.
A digest reaches the provider of the model that reads it
(sessions.md).
What install writes. The files of the harness table in
operate.md, and in CLI mode one section in each
harness's global instruction file (its Rule column), between
<!-- agentfeedback:begin ... --> and <!-- agentfeedback:end -->; unless
--with-triage=false (init --no-triage), also the binary's copy of the
agentfeedback-triage skill in each skills directory, never over a skill
or triage directory or link it did not write (it takes over only one that
holds exactly the files it would write). It reads, never writes, Claude
Code's plugins/installed_plugins.json and Codex's config.toml to find an
agentfeedback plugin, which then provides the skill instead. Each
file is backed up before its first change (<file>.agentfeedback-backup) and recorded in the install
manifest like the other files; the text between the markers belongs to
install, which replaces it in place and removes it by its markers even
when edited, and --check only reads. A repository's instruction file
(AGENTS.md, else an existing CLAUDE.md) is written only by
install --project, after a confirmation that names the repository and
the file, or with --yes. It gets the pointer text only, between the
same markers, is never committed by the tool (reviewing the commit is the
consent), is removed by its markers with install --project --uninstall
(the file is deleted only when nothing remains), is never followed through
a symbolic link (a linked file is refused), and is not recorded in the
manifest and not backed up.
What config writes. The settings menu and config set edit only the
allowlisted machine settings, using an atomic
0600 replacement that retains unknown keys and credentials, not TOML comments.
CLI config writers serialize the whole read/edit/write operation with a persistent
config.toml.lock file. Symlinked configs and ambiguous case variants of known
keys are refused. Destination updates through init and doctor --init retain
machine settings, including collection restrictions.
The settings menu and config set never change the destination, API key, repository
narrowing or organisation policy. Read output masks the API key; policy display can contact the configured
server but sends no transcript. Menu harness actions use the same installer
and ownership rules described above, for one explicitly selected harness.
What the client collects into context for a friction, all best-effort
(--dry-run shows it before anything is sent):
- Project: the working directory's folder name (
~for the home directory), and inside a Git repositoryrepo_root(~-relative under the home directory), the remote URL with credentials, query string and fragment stripped, branch, commit, dirty flag, upstream, tag and default branch, read from.gitmetadata (withgitwhen it is onPATH). - Machine: OS, architecture, client version, and the short hostname as
machine. - Harness and session: the harness, model, session id, agent, effort and
profile, from a fixed allow-list of environment variables looked up by
name (for OpenCode 2,
OPENCODE_SESSION_ID, which also tells it from OpenCode V1). - The event time, and any non-code keys you pass (
app,workspace,url,channel,task_id,workflow).
Opt-in only: the full working directory, which holds the home path and so
the username (cwd = true under [context] in config.toml), and for submit review the reviewer outputs and prompt
(--include-outputs). Never collected: the username or home path (except through that opt-in), IP or MAC
addresses, serial numbers, other processes, files other than .git
metadata and .agentfeedback.toml, and environment variables not on the
allow-list. The [collect] table in config.toml and a repository's
.agentfeedback.toml can only narrow this. Even so, a remote, a branch or
a hostname can reveal private repository names and internal hosts, and
report prose holds whatever the agent wrote.
submit <kind> --scrub, and any submission whose context.origin is
session-scan, replaces known secret formats in every string value of the
body (summary, payload, context, any other member) with
[REDACTED:<class>] before checking or sending; member names are never
changed, and a non-empty string under a secret-named member (password,
token, api_key, client_secret and similar) is redacted whole. The
outcome line carries one scrubbed warning per member changed.
submit review <run_dir> and submit review --sweep do not scrub, and spool
entries queued before are sent as they were. The server does the same at
ingest with INGEST_SCRUB=on (default off), before decoding, silently: the
stored text carries the markers, the response has no warning, and the server
log records the counts per class, never the text. The classes, in the order
they apply: private_key, jwt, aws_access_key, gcp_api_key,
github_token, gitlab_token, slack_token, stripe_key,
anthropic_key, openai_key, bearer_token, url_credentials, env_line
(NAME=value lines whose name has a segment ending in KEY, TOKEN, SECRET,
PASSWORD, PASSWD, CREDENTIAL(S), AUTH or PRIVATE) and assignment
(password=, token: and similar, and secret-named members). This is
defence in depth: it detects these formats and no others, not secrets in
prose, and never member names. import does not scrub. Otherwise the server
stores what it receives and redacts nothing.
The harness hooks (agentfeedback hook) read only the payload the harness
passes on stdin, never the transcript or any other file it names, call no
model and send nothing about the session. They keep counters per session in
the cache directory (0700 directories, 0600 files, no command text,
deleted after 7 days); the short note they may show quotes the failed
command, cut to 80 bytes after the same secret-format replacement as
--scrub. A note suggests a friction report and never blocks or redirects
a tool call or continues a stopped turn. Amp appends the note to the tool's
error text while preserving its status and output; other adapters deliver
additional context without replacing the original result. The client log
keeps a submission's context.session_id, and no other context; a friction
report with one also leaves an empty marker
named by a digest of the session id, so the hook can tell a session already
filed a report without reading the log.
Stored text is untrusted. A processor treats reports, suggested fixes and event payloads as evidence to verify, never as instructions to execute. The triage skill's checkout rule exists for this reason.
The triage skill's optional TypeSafe helper is a separate disclosure boundary,
not a server feature. It sends selected report prose to api.typesafe.ai only with explicit
per-repository approval; a key alone never enables it. Reports can contain
secrets in prose, so inspect the local preview before sending. Advice cannot
authorize edits or processed marks. A batched request shares one state
among up to 8 reports, all from approved repositories. scripts/eval-cluster.py
is the same boundary for calibration: it sends nothing unless every labelled
report's exact remote is approved and --live is given. The
clustering reference (skills/agentfeedback-triage/reference/clustering.md)
defines the fields, consent rules and manual fallback.
One SQLite file in a Docker volume, or in local mode
~/.local/share/agentfeedback/agentfeedback.db, readable by anyone who can
read the volume, the file or a backup. Encrypt and restrict backups; they hold everything
agents ever reported. Retention is manual (see
operate.md). Producers keep unsent payloads in the
same data directory, ~/.local/share/agentfeedback/spool/, until delivered
or aged out; the client creates that directory 0700 and its files 0600,
and agentfeedback doctor reports a path other users can reach (see
operate.md).
Do not post keys, telemetry or exploit details in public issues. Contact the maintainer privately first.