Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

alfred-os/AlfredOS

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2,201 Commits
2,201 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

AlfredOS

Discord Ask DeepWiki

An open-source, self-hostable, multi-user, multi-persona, security-hardened agentic OS.

Status: Pre-implementation. The design is in PRD.md; the operating manual for AI agents working in this repo is in .rulesync/rules/CLAUDE.md.

Community: Join the AlfredOS Discord to ask questions, share builds, or follow development.

What is AlfredOS?

AlfredOS is a long-lived agentic runtime that hosts AI personas — specialized agents with their own purposes — and lets them:

  • Converse with users across pluggable platforms (Discord + Telegram + TUI for MVP).
  • Share multi-layered memory (working, episodic, semantic, vector, knowledge graph) per user, with auto-save and auto-recall.
  • Coordinate with each other, with explicit safety rails (loop detection, budget caps, audit visualization).
  • Extend themselves with new skills under a reviewer-gated change process — never validating their own work.
  • Run continuously as a bounded autonomous OODA loop, with full audit trail and one-command rollback.

AlfredOS is hardened from day one against prompt injection, credential leakage, and PII exfiltration. Trust tiers, a dual-LLM split, a capability-gated tool layer, outbound DLP, secret brokering, canary tokens, and a cross-provider reviewer agent are all part of the MVP — not later additions.

Alfred (no "OS") is the name of the default persona — the head butler — who ships enabled out of the box. Specialist personas (Lucius, Oracle, Diana) are bundled as examples; operators enable them as needed.

Quickstart

Not yet implemented. Target experience for v0.1:

git clone https://github.com/alfred-os/AlfredOS
cd AlfredOS
cp .env.example .env       # then set ALFRED_QUARANTINE_PROVIDER_API_KEY (see below)
bin/alfred-setup.sh        # macOS/Linux; on Windows, run inside WSL
docker compose up -d
alfred user add --authorization operator --name "Your Name"   # one-time
alfred chat                 # start a TUI conversation

A provider key is required before the first docker compose up -d. ALFRED_QUARANTINE_PROVIDER_API_KEY in .env is the credential for the quarantined half of the dual-LLM split, which now makes real provider calls. With it unset the core exits 2 (quarantine_provider_key_unset) and crash-loops under restart: unless-stopped. This is deliberate — a real client on a placeholder key would be a silently dead LLM — but it means a keyless first run does not start. bin/alfred-setup.sh warns when the key is missing; it cannot seed one for you.

Precisely: the refuse-boot is gated on comms being enabled (settings.comms_enabled_adapters). With no adapters enabled there is no quarantine path, so no key is needed and the core boots fine. That is not the quickstart above: docker-compose.yaml defaults ALFRED_COMMS_ENABLED_ADAPTERS to ["alfred_tui"], so the compose stack — the path this README documents — does enable comms and does require the key. If you run the core outside compose with ALFRED_COMMS_ENABLED_ADAPTERS unset, the key is genuinely optional.

docker compose up -d now starts alfred-core as a long-running daemon (alfred daemon start, restart: unless-stopped) — earlier releases ran it as a one-shot command runner. One-off subcommands still work via docker compose run --rm alfred-core <cmd> (migrate, user add, chat, …) because run overrides the service command. Run bin/alfred-setup.sh before docker compose up -d: it seeds the audit.hash_pepper and provisions secrets the daemon requires to boot. Skip it and the daemon refuse-boots and, under restart: unless-stopped, crash-loops. The script seeds what it can and warns about what it cannot — ALFRED_QUARANTINE_PROVIDER_API_KEY has to come from you.

docker compose up -d also starts alfred-gateway — the always-up resumable front door that holds an alfred chat session across a core restart. As of this release the gateway links to the core: the daemon binds comms-tui.sock on the shared alfred_run volume and the gateway dials it (its compose-internal alfred-gateway:9464/metrics gateway_core_link_up gauge reads 1 once both are up; see ops/prometheus/prometheus.yml).

alfred-core serves its own Prometheus endpoint at alfred-core:9465/metrics — the core-owned series (quarantine capability revocations, comms dispatch, supervisor action durations, plugin/DLP scan timings). Like the gateway's, it is compose-internal and never host-published: there is no host port to curl. The bind port comes from ALFRED_CORE_METRICS_PORT (default 9465), which exists so the daemon and alfred daemon healthcheck resolve one port from one place — treat it as fixed, since the bundled scrape config targets alfred-core:9465 literally (Prometheus cannot expand env vars in a scrape target). To check the endpoint from the host: docker compose exec alfred-core alfred daemon healthcheck (also wired as the service's container healthcheck, so docker compose ps shows unhealthy if the metrics bind failed). Note: the alfred_run volume inherits ownership from the image on first creation; if you are upgrading an older deployment that already has an alfred_run volume with the wrong owner, run docker compose down && docker volume rm <project>_alfred_run before up -d so it is re-created owned by the alfred user.

docker compose up -d also starts alfred-prometheus and alfred-grafana — default-on, internal-only services. alfred-prometheus scrapes alfred-core:9465 and alfred-gateway:9464; alfred-grafana queries Prometheus through its server-side datasource proxy and gives you a dashboard, so the QuarantineCapabilityRevoked alert (and every other core/gateway series) is queryable, not just defined. Reach Grafana at http://127.0.0.1:3000 (Linux) or see the per-platform notes in the observability stack runbook for OrbStack/Docker Desktop. bin/alfred-setup.sh seeds a strong GF_SECURITY_ADMIN_PASSWORD into .env for you — Grafana refuses to start on the default admin:admin (exit 78; the runbook covers this if you hit it).

AppArmor hosts (Ubuntu 23.10+ and other userns-restricted Linux): the dual-LLM quarantine child runs under bubblewrap, which builds an unprivileged user namespace. On hosts with kernel.apparmor_restrict_unprivileged_userns=1 (the modern Ubuntu default) the kernel refuses that namespace unless the container runs under an AppArmor profile carrying userns,. bin/alfred-setup.sh loads the bundled docker/apparmor/alfred-bwrap profile for you. If you run docker compose directly (skipping the setup script), load it first or alfred-core crash-loops with bwrap: No permissions to create new namespace:

sudo apparmor_parser -r docker/apparmor/alfred-bwrap

Run all docker compose commands from the repository root: the compose security_opt: seccomp=docker/seccomp/alfred-bwrap.json path resolves relative to the compose-invocation directory, not the compose file. macOS and non-AppArmor Linux hosts need none of this (the security_opt lines are runtime no-ops there). The bundled PBS interpreter adds roughly +110 MB to the alfred-core image.

macOS host access to Postgres (G7-3 connectivity-free core)

alfred_internal is internal: true, so on Docker-Desktop/OrbStack (macOS) the alfred-postgres host-published port 5432 is not forwarded — psql -h localhost from a Mac host will not connect. The compose-internal core reaches Postgres over alfred_internal, and the dev test loop uses testcontainers, so neither is affected. For a one-off host query, exec into the network: docker compose exec alfred-postgres psql -U alfred -d alfred. On Linux, published ports NAT independently of the internal network, so host access still works.

Mandatory egress proxy

ALFRED_EGRESS_PROXY_URL is mandatory — the core has no direct-egress fallback.

DeepSeek: if you override ALFRED_DEEPSEEK_BASE_URL from its default, set the same value on both alfred-core and alfred-gateway. The gateway derives its destination allowlist from that variable; a mismatch means the core dials a host the gateway denies.

Anthropic: the gateway allowlist is hardcoded to api.anthropic.com. A custom Anthropic endpoint is not supported — it would be denied by the allowlist.

Enable Discord (Developer Mode walkthrough)

AlfredOS ships a DM-only Discord adapter hosted by the gateway. It is off by default — a stock docker compose up -d hosts no comms adapter at all, so the gateway boots healthy with zero configuration (ADR-0054). Enabling Discord is opt-in and requires both a bot token and the hosted-adapter flag below — the token alone does not turn Discord on. Operator workflow for a fresh deploy:

  1. Create a bot in the Discord developer portal. Visit https://discord.com/developers/applications, create a new application, then create a Bot user under it.

  2. Enable the Message Content gateway intent. Bot settings → Privileged Gateway Intents → toggle Message Content on. Without this, the adapter sees every DM as empty content and never reaches the orchestrator.

  3. Copy the bot token. Bot settings → Reset Token → copy.

  4. Set the token and opt in, in .env. Open your .env file (copy from .env.example if you have not already) and set both lines:

    ALFRED_DISCORD_BOT_TOKEN=YOUR-TOKEN-HERE
    ALFRED_GATEWAY_HOSTED_ADAPTERS=["alfred_discord"]

    Re-running bin/alfred-setup.sh writes the second line for you: it seeds ALFRED_GATEWAY_HOSTED_ADAPTERS whenever it finds a token already in .env. Skip the second line and the gateway still hosts nothing — --wait-ready discord in step 8 below then exits 3 (the adapter isn't in the hosted set, so it can't be resolved), rather than timing out. The token itself is read by alfred-core on boot and delivered to the gateway-hosted Discord child over fd-3 at spawn time; the gateway and child never hold the token in their environment.

  5. Invite the bot to a server with the bot scope. AlfredOS only reads DMs; you do not need any guild-message permissions yet.

  6. Bind your Discord user to the operator identity. In Discord: Settings → Advanced → Developer Mode → right-click your user → Copy ID. Then on the host:

    alfred user bind --slug <your-operator-slug> --platform discord --platform-id <snowflake>

    The setup script offers an interactive prompt for this in its final step.

  7. Start the gateway.

    docker compose up -d alfred-gateway
  8. Verify the adapter is ready. Once the gateway is up, run:

    alfred gateway adapters --wait-ready discord

    This polls until the Discord adapter reports ready or the timeout expires. Exit 0 means the adapter reached on_ready and is accepting DMs. Then send the bot a DM from your Discord account; the round-trip lands through the orchestrator with audit + budget + episodic memory + DLP all in place.

Secrets file — permission propagation matrix

~/.config/alfred/secrets.toml is plaintext for Slices 2 and 3. ADR-0012 documents this as a known risk; secrets management hardening (containerised secret broker) ships in Slice 4 per ADR-0015.

The broker reads this host-default file when ALFRED_SECRETS_FILE is unset (completing ADR-0012). If you already keep secrets there — or your ~/.config is a git repo — read the upgrade note first.

In the meantime:

  • macOS: Docker Desktop maps the host file's uid/gid to the container uid/gid directly; chmod 600 on the host applies inside the container too. The setup script runs export UID GID because macOS bash 3.2 does not export UID by default.
  • Linux: user: "${UID:-1000}:${GID:-1000}" in docker-compose.yaml resolves to the operator's real uid/gid; the bind-mount's chmod 600 is enforced by the kernel exactly as on the host.
  • WSL2: same as Linux, with the caveat that running docker compose from PowerShell (vs wsl) sees a different uid namespace. Run the setup script from inside WSL to keep the perms consistent.

Backup-vector reminder: if you back up your ~/.config with restic, borg, or similar, exclude ~/.config/alfred/secrets.toml or the backup will contain plaintext API keys. The ALFRED_DISCORD_BOT_TOKEN lives in .env (not in secrets.toml); exclude .env from any backup that should not retain plaintext credentials. The containerised secret broker lands in Slice 4; until then, the operator owns both exclusions.

Configuration

Operator-facing environment variables live in .env.example; copy it to .env and edit. The Slice-3 trust-boundary section documents the plugin-launcher, capability-gate, and supervisor knobs (sandbox policy directory, plugin UID, perf-gate force-run, redis maxmemory, state-git path).

Environment resolution (ALFRED_ENVIRONMENT)

ALFRED_ENVIRONMENT is a mandatory, closed-vocabulary setting (development / production / test) — alfred daemon start refuses to boot without a recognised value. It resolves via a three-layer precedence, highest wins:

  1. The ALFRED_ENVIRONMENT process environment variable (exported in your shell, or set by docker compose / systemd).
  2. /etc/alfred/environment — a root-owned system file holding a bare value (no KEY= prefix).
  3. Your .env file — lowest precedence, gap-fill only: it supplies a value only when neither of the above is set, and it can never unlock the gateway's dev/test-only launch-target-override escape hatch even when its value is otherwise valid.

.env.example ships ALFRED_ENVIRONMENT=production uncommented — copy it to .env as-is, or export the env var, or write /etc/alfred/environment, to satisfy this. See ADR-0053 for the full precedence chain, the fail-closed behaviour on an unreadable /etc, and why a .env-sourced value can never unlock that escape hatch.

Distinct from ALFRED_ENV below. The two names look alike but gate unrelated decisions: ALFRED_ENVIRONMENT (this section) is the closed triple gating boot and the launch-target override; ALFRED_ENV (next section) is a free-form dev/production selector gating which capability-gate implementation gets constructed. Setting one does not set the other — .env.example comments both to keep them from being confused for one another.

Gate selection

Distinct from ALFRED_ENVIRONMENT above — this section's ALFRED_ENV gates the capability-gate implementation, not boot or the launch-target override.

The capability gate has two implementations: RealGate (Postgres-backed) and DevGate (fail-open stubs, development-only). Selection is effectively opt-out of DevGate: only ALFRED_ENV=development (or unset/empty/whitespace, which short-circuits the bootstrap to DevGate) selects the stub. Anything else — including typos — falls through to RealGate:

ALFRED_ENV value Gate constructed
development DevGate (fail-open stubs)
Unset, empty, or whitespace-only DevGate
Anything else (production, staging, prdouction typo, ...) RealGate

This means a typo in a production deployment safely falls through to RealGate. The matching startup log event (bootstrap.gate_selected, INFO-level) carries the exact env value the bootstrap read so an operator who set ALFRED_ENV=prod (instead of production) can confirm which gate they ended up on. The plugin runbook (docs/runbooks/slice-3-plugins.md) walks through the full Slice-3 deployment, including the launcher and the supervisor.

Design

See PRD.md for the full design, including:

  • Architecture overview
  • The 7 capability pillars + persona system
  • Security model and prompt-injection defenses
  • Memory model
  • Reviewer-gated self-improvement
  • Token caching and cost control
  • Deployment and self-healing
  • MVP scope vs. roadmap

Contributing

Contributions welcome. Read CONTRIBUTING.md and our CODE_OF_CONDUCT.md. Contributions are licensed under the project's Apache-2.0 license.

For Python work specifically: docs/python-conventions.md is the canonical reference (tooling, types, errors, async, testing, security, i18n). AI agents should dispatch the alfred-python-developer subagent, which applies it without being asked. The docs/adr/ directory holds the Architecture Decision Records that explain why the conventions look the way they do. The most recent — ADR-0014: pluggable hooks for every action — records the Slice 2.5 hooks subsystem.

If you (or an AI agent) are contributing to this repository, also read .rulesync/rules/CLAUDE.md for repo conventions, security rules, and the self-improvement process.

Security

If you have found a security vulnerability, do not open a public issue. Use GitHub Security Advisories to report privately. See SECURITY.md for details.

License

AlfredOS is licensed under the Apache License, Version 2.0. See the LICENSE and NOTICE files for the full terms.

Plugins communicate with the core via the MCP subprocess boundary (stdio / HTTP) and are not considered derivative works; plugin authors may license their work however they choose.

About

An open-source, self-hostable, multi-user, multi-persona, security-hardened agentic OS.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.