Instructions for an AI agent (or human) working in this repository. Read this before changing code.
Albert is a personal, always-on assistant built as an assembly: it runs as a
single Cogitator inside the Octo
event-driven runtime (the Reaction skeleton — bus, connectors, supervision),
thinks with persistent kaeru memory, and
reaches the world through connectors (Telegram, calendar, a scheduler). Talk to it;
it remembers, reminds, and keeps a verifiable scratchpad for multi-step work.
Single binary crate, Rust edition 2021 (toolchain 1.85+). Octo and kaeru are
git dependencies (see Cargo.toml) — one repo each, many crates; there is no
sibling-checkout requirement.
Deeper design docs: docs/ (architecture / configuration / structure /
deploy). This file is the working rules; docs/ is the reference.
Orient by module before changing code. Everything is under src/:
| Module | Owns |
|---|---|
main.rs |
Wiring: load .env + albert.toml, open the kaeru vault, build history / scheduler / scratchpad / prompt loader; register connector factories + from_config_file, or fall back to console; run Octo. |
cogitator.rs |
AlbertCogitator — the Cogitator: perceive → reflex → assemble-context → rig tool-loop → reply, for chat.message and alarm.fired. Builds the agent + toolset; picks the LLM client by Config::auth (API key vs ChatGPT subscription). |
openai_auth.rs |
ChatGPT-subscription token store: read/write a codex-style auth.json; ensure_fresh returns the Subscription (access token + account id), refreshing the access token in place before it expires. |
openai_login.rs |
albert login — the interactive OAuth (PKCE) sign-in: loopback callback server on :1455 with a stdin paste fallback, code exchange, token save. |
codex_model.rs |
CodexResponsesModel — a rig CompletionModel for the Codex backend: routes the non-streaming tool-loop through rig's streaming path (the endpoint is streaming-only) and folds the SSE back into a response. |
codex_http.rs |
CodexHttp — a rig HttpClientExt that rewrites the Codex request/response on the wire (store:false, system→developer, relaxed tool schemas, injected SSE content-type) so rig's serializer + parser stay reused. |
acl.rs |
Owner-only Telegram ACL admin reflex (/allow /deny /allowed) — deterministic, no LLM in a security action. |
routines.rs |
Proactive routines: idempotently seed the base memory-reflection alarm on startup. |
scratchpad.rs |
The loop scratchpad: channel-keyed store + the scratchpad_* rig tools. |
prompt.rs |
PromptFiles — load soul.md + system.md into RAM, hot-reload by mtime. |
config.rs |
Config — parse albert.toml (serde + toml), resolve the LLM secret from env, resolve relative paths against the config dir. |
history.rs |
Bridge to Octo's octo-history: the per-channel transcript (hot-context tier). |
console.rs |
ConsoleConnector — stdin/stdout channel, the Telegram stand-in for dev. |
error.rs |
The crate error type — explicit #[from] variants, no anyhow. |
Rules of thumb:
- Connectors (Telegram, calendar) are config-driven through Octo's
from_config_file+ manifests underconfig/connectors/. Add/adjust a connector there, not in code (registering its factory inmain.rsis the only code touch). - A connector that advertises a
descriptionappears automatically in the cogitator's dispatch catalog — the agent reaches it via the onedispatch_to_connectortool. Adding an organ needs zero cogitator change. - Octo and kaeru are the source of truth for their types — consume them, don't
duplicate. Bump the pinned rev/tag in
Cargo.tomlto move either forward.
cargo build— quick check after changes.cargo test— run tests (e.g. the scratchpad store test).cargo run— run Albert. Telegram ifOCTO_TELEGRAM_TOKENis set, else a console channel. Config fromalbert.toml; secrets from.env.cargo run -- login— sign in with a ChatGPT subscription (writes tokens to the[subscription] auth_jsonpath). Only needed whenauth = "subscription".cargo build --release— the deploy binary (optimized profile: LTO + strip).
Deployment (build here, ship the binary; the target is small): see
docs/deploy.md.
Keep code modular, grouped by purpose, one concern per file. Cap each file at ~500 lines (hard limit 600). Split a module when:
- it passes ~500 lines, or
- more than a few cohesive concerns accumulate in one file.
cogitator.rs was split this way — the ACL admin moved to acl.rs, routine seeding
to routines.rs. When a module grows past a flat file, prefer a
mod.rs-with-submodules layout and keep shared cross-submodule types in the parent
mod.rs.
- Import the final entity, in full, by name — structs, enums, functions, traits,
constants.
Duration::from_secs(..), notstd::time::Duration::from_secs(..);info!(..), nottracing::info!;Utc::now(), notchrono::Utc::now(). - Group items from the same module/crate path in braces: one
use std::{sync::Arc, time::Duration};, oneuse octo_core::{...};, oneuse crate::{...};— never oneuseline per item from the same path. - No module paths in code bodies. Import the leaf; don't
use moduleand then writemodule::Type::methodthroughout. Associated fns on an imported type (Duration::from_secs) are fine — the type is imported. - No glob imports (
use foo::*) in implementation code. - On name collisions, alias the leaf (
use rig::http_client::Error as HttpError;) or use a leading::to disambiguate an extern crate from a same-named local module (use ::config::Config;). - Group blocks: std, then third-party crates, then
crate::— blank line between groups. - Reasonable exemptions: attribute macros stay pathed (
#[tokio::main]).
In short: explicit, brace-grouped imports; no long module paths scattered through the code body.
- No emoji in code or logs. Strip pictographic emoji from source and log
output; arrows / quotes / bullets are fine. (The bot's chat replies carry an
in-character emoji persona — that lives in
soul.md, not in code, and is a deliberate product choice, not a violation of this rule.) - No
anyhow. Errors are explicit —error.rsdefinesError(athiserrorenum with#[from]variants) andResult<T>. Add a variant for a new failure mode; don't stuff context into an existing one orformat!at call sites. - Commit style: Conventional-Commits-style lowercase prefixes —
feature:,fix:,chore:,docs:,git:(notefeature, notfeat). NoCo-Authored-Bytrailer. - Config-as-data: nothing tunable is hardcoded. Albert-level config in
albert.toml; connector manifests underconfig/; persona + instructions insoul.md/system.md(hot-reloaded). Secrets live only in the environment, named in the TOML by their env-var name.
- The repo ships placeholder manifests (
owner_chat, Yandexlogin). Real values live only on the target machine, never committed. - Runtime state is gitignored:
state/(scheduler alarms) andconfig/connectors/telegram/telegram_acl.json(the mutable ACL).
- The "graph" direction is the agent building — and reusing — its own task structure (grown from the per-turn scratchpad toward self-authored, reusable task graphs). It is not a LangGraph-style control-flow engine over fixed stages; do not build one.
- Skills (declarative SKILL.md registry) and a sandboxed execution substrate (forkd) are planned, not present.
- Packaging: a
contrib/folder and a.debare future work.