AGENTS.md
Rama main · réplica de lectura del repositorio original.
# AGENTS.md — Albert
Instructions for an AI agent (or human) working in this repository. Read this
before changing code.
## Project Summary
`Albert` is a personal, always-on assistant built as an **assembly**: it runs as a
single `Cogitator` inside the [Octo](https://github.com/LamantinAI/octo)
event-driven runtime (the Reaction skeleton — bus, connectors, supervision),
thinks with persistent [kaeru](https://github.com/LamantinAI/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/`](docs/) (architecture / configuration / structure /
deploy). This file is the working rules; `docs/` is the reference.
## How To Work In This Repository
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/` | `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). |
| `octo-openai-auth` | 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). |
| `memory/` | Embedded kaeru or a dedicated MCP memory backend; scoped tool adapters and startup migration of the `albert` initiative. External memory is separate from the connector catalog. |
| `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:
1. Connectors (Telegram, calendar) are **config-driven** through Octo's
`from_config_file` + manifests under `config/connectors/`. Add/adjust a
connector there, not in code (registering its factory in `main.rs` is the only
code touch).
2. A connector that advertises a `description` appears automatically in the
cogitator's dispatch catalog — the agent reaches it via the one
`dispatch_to_connector` tool. Adding an organ needs **zero cogitator change**.
3. Octo and kaeru are the source of truth for their types — consume them, don't
duplicate. Bump the pinned rev/tag in `Cargo.toml` to move either forward.
## Local Runbook
- `cargo build` — quick check after changes.
- `cargo test` — run tests (e.g. the scratchpad store test).
- `cargo run` — run Albert. Telegram if `OCTO_TELEGRAM_TOKEN` is set, else a
console channel. Config from `albert.toml`; secrets from `.env`.
- `cargo run -- login` — sign in with a ChatGPT subscription (writes tokens to the
`[subscription] auth_json` path). Only needed when `auth = "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`](docs/deploy.md).
## Module Organisation
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/` 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`.
## Test Layout
- Unit tests belong in inline `#[cfg(test)] mod tests` blocks in the owning `mod.rs`
or crate `lib.rs`. Do not add standalone `src/**/tests.rs` files.
- Keep implementation helpers in cohesive sibling files when a module grows; keep
the facade and its tests together and respect the 600-line hard limit.
- Tests exercising only the public API across components belong in the crate's
`tests/` directory. Shared integration fixtures may live in `tests/common/mod.rs`.
- Async integration scenarios must have a bounded wait. A connector's local shutdown
token stops that connector; use the runtime control contract to stop the runtime.
## Import Rule (strict)
- **Import the final entity, in full, by name** — structs, enums, functions, traits,
constants. `Duration::from_secs(..)`, not `std::time::Duration::from_secs(..)`;
`info!(..)`, not `tracing::info!`; `Utc::now()`, not `chrono::Utc::now()`.
- **Group items from the same module/crate path in braces**: one
`use std::{sync::Arc, time::Duration};`, one `use octo_core::{...};`, one
`use crate::{...};` — never one `use` line per item from the same path.
- **No module paths in code bodies.** Import the leaf; don't `use module` and then
write `module::Type::method` throughout. 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.
## Conventions
- **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.rs` defines `Error` (a `thiserror`
enum with `#[from]` variants) and `Result<T>`. Add a variant for a new failure
mode; don't stuff context into an existing one or `format!` at call sites.
- **Commit style**: Conventional-Commits-style lowercase prefixes — `feature:`,
`fix:`, `chore:`, `docs:`, `git:` (note `feature`, not `feat`). No
`Co-Authored-By` trailer.
- **Config-as-data**: nothing tunable is hardcoded. Albert-level config in
`albert.toml`; connector manifests under `config/`; persona + instructions in
`soul.md` / `system.md` (hot-reloaded). Secrets live only in the environment,
named in the TOML by their env-var name.
## Secrets & per-target config
- The repo ships **placeholder** manifests (`owner_chat`, Yandex `login`). Real
values live only on the target machine, never committed.
- Runtime state is gitignored: `state/` (scheduler alarms) and
`config/connectors/telegram/telegram_acl.json` (the mutable ACL).
## Out Of Scope / Future
- 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 `.deb` are future work.
## Repository privacy (mandatory)
Store only anonymized technical requirements and decisions. Never place private
or identifying information in any Git repository, public or private, including
ignored files: developer names or personal identifiers, real local paths,
private infrastructure addresses,
private work dates or timelines, correspondence, operator or agent messages,
quotes, paraphrases, or derived disclosures.
This applies to source, documentation, comments, configuration, logs, artifacts,
commit and pull-request text, and metadata. Keep operational records outside
repository working trees in approved private storage.
General instructions to save, commit, or publish do not waive this rule.
Exceptions require explicit permission for the specific information. Before
staging or publishing, review direct and indirect disclosure. Use neutral
placeholders, repository-relative paths and reserved example addresses. Preserve
required licenses and public attribution; existing history does not authorize
copying private details. Never reintroduce unsanitized history during a merge.