Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ CHANGELOG entry.

### Added

- **`atomic-agents init` wizard** ([#94](https://github.com/dep0we/atomic-agents-stack/issues/94) -- init-wizard arc **PR 1 of 2**). Operators can now run `atomic-agents init <name>` and have a callable home-user agent in under 10 minutes, including time spent thinking about what the agent should be. The wizard walks seven structured questions (name, mission, scope in/out, autonomy, voice, communication preferences, hard refusals), composes `persona/{IDENTITY,SOUL,USER}.md` + `tools.md` + `model.md` + `memory/INDEX.md` + `wiki/INDEX.md` deterministically from the answers, creates empty `journal/` and `log/` directories, and ends with a `doctor` health check on the new agent followed by an opt-in test call against the configured LLM. `--from-template advisor` skips the interview and scaffolds a Caldwell-shaped starter agent in under 30 seconds. `--list-templates` enumerates available templates. Re-running `init` on an existing agent name offers Overwrite (atomic backup+restore: rename existing to `.bak.<UTC-ISO>`, write fresh, rmtree backup on success, restore on failure) or Cancel (default; pressing Enter is a no-op exit). The wizard refuses non-interactive terminals on the interactive Q&A path with a plain-English pointer to `--from-template`; `--from-template <name>` and `--list-templates` work in CI without a terminal. The opt-in test call catches `anthropic.RateLimitError`, `anthropic.AuthenticationError`, `anthropic.APIConnectionError` / `httpx.ConnectError`, `AtomicAgentsError`, and generic `Exception` with operator-friendly messages; every exception path exits status 0 (the scaffold succeeded; the call is best-effort). The wizard warns before any file write when `ATOMIC_AGENTS_PERSONA_BACKEND_URL` is set non-empty (the case where wizard output diverges from PersonaBackend's view); decline exits 0 with zero files written. ANTHROPIC_API_KEY pre-flight uses `_llm._get_key` directly so operators with the key in macOS Keychain or `~/.config/atomic_agents/keys.json` are not false-negatived. Closes the half-day deploy: the brief's seven pain points compress from approximately 4-5 hours total to approximately 5-10 minutes, with `mcp.md` configuration (pain 5) the only one explicitly deferred (`doctor` skips cleanly when absent). `rich` adopted as the canonical operator-facing CLI rendering library (documented in spec/35 as the rendering primitive future arcs migrate `doctor`, `bundle`, and `corpus` output to). spec/35 ships with 14 normative MUSTs that the implementation honors and that the adversarial review army verifies. New `atomic_agents/init/` package with `wizard.py` (the interactive flow), `constants.py` (the single source of truth for action class vocabulary, template variable names, error messages, reserved names, and the `agent_name` regex), and `templates/advisor/` (seven `.md` files using `string.Template` `${var}` substitution via `safe_substitute`). cli.py edits are bounded to one lazy import (inside `_cmd_init`, matching the existing pattern at `_cmd_doctor` and `_cmd_persona`), one `sub.add_parser("init", ...)` block with arguments, one dispatch case in the doctor/persona/corpus early branch, plus two Usage / Subcommands docstring lines. 50 net new tests across 5 files (`test_init_cli.py` argparse + dispatch, `test_init_wizard.py` Q1-Q7 + Q4 preset/customize + non-TTY + persona-backend warning + collision recovery + OSError translation + template substitution + agents_root single-resolution, `test_init_templates.py` advisor structure + locked variable conformance, `test_init_smoke.py` end-to-end with mocked LLM, `test_init_wheel_install.py` opt-in wheel build + install verification gated by `RUN_WHEEL_INSTALL_TESTS=1`). Test suite: 2889 + 48 skipped to 2939 + 50 skipped, zero regressions. Pre-impl prep (4 parallel subagents) caught 8 SEVERE + 21 HIGH + 16 MEDIUM + 8 LOW findings across the brief BEFORE any code shipped, including the Q4 action-class vocabulary mismatch (the brief used shorthand `audit`/`judge` where spec/28 defines `allow_with_audit`/`judge_required`), the hatchling force-include misconfiguration (templates auto-include via existing `packages = ["atomic_agents"]`; the brief's "add a force-include line" was a no-op or build break), the pre-flight resolver chain incompleteness (Keychain and `keys.json` operators got false-negative on env-var-only check), the USER.md "Things to avoid" section missing (Q7 originally routed only to tools.md; now renders to both with surface-appropriate phrasing per the persona-vs-enforcement separation), and the overwrite atomicity gap (`rm -rf` then write is not crash-safe; backup+restore preserves operator work).

- **CorpusBackend wiring + per-runner kwargs + delegate threading + doctor + IRON RULE regression suite** ([#65](https://github.com/dep0we/atomic-agents-stack/issues/65) -- CorpusBackend arc **PR 3 of 4**). The wiring PR turns the Protocol scaffolded in PR 1 and the SQLite impl shipped in PR 2 into something single-host operators can pin via one env var. `ATOMIC_AGENTS_CORPUS_BACKEND=sqlite` now resolves to `SQLiteCorpusBackend` with a sensible default db at `<agent_root>/.corpus.db` and `agent_scope=<agent_root.name>` (mirroring the `AgentProfileBackend` and `ToolRegistryBackend` precedent). `ATOMIC_AGENTS_CORPUS_BACKEND_URL` overrides the default path; both `filesystem://...` and `sqlite:///path?agent_scope=...` URLs route through their respective factories. `AtomicAgent` gains a `corpus_backend` constructor kwarg + class-level annotation; resolution defaults via `get_default_corpus_backend(self.agent_root)` when not supplied. `_corpus_backend_was_explicit` flag tracking on `self` (mirrors PersonaBackend D-ER-2 at `agent.py:431`) drives explicit-only threading at `delegate()`: default-resolved backends do NOT leak the coordinator's `agent_root` into delegates because corpus is per-agent semantic context, not fleet-scoped. Per-runner kwargs land on `OutcomeRunner` (threads through to the internal `AtomicAgent` at `outcome.py:255`), `EvalRunner` (threads at `eval.py:363`), and `DreamRunner` (stored as `self._corpus_backend` for API parity; no internal `AtomicAgent` construction site in v1 -- documented in the runner). `doctor.check_corpus_backend` lands as the 12th `check_*_backend` in `doctor.py` with PASS/WARN/FAIL ladder: PASS on healthy filesystem or sqlite construction + successful stats probes on both wiki and raw corpora; WARN on the page-count cliff (any corpus exceeding ~1000 pages on a backend that advertises `supports_full_text_search=False`, with the hint `"Set ATOMIC_AGENTS_CORPUS_BACKEND=sqlite for indexed query performance. Filesystem keyword grep at this scale can take seconds per query."`); WARN on operator-implicit URL configuration (URL set, backend id unset; surfaces the implicit-default resolution path rather than forcing operators to debug which backend is active); FAIL on construction error or stats() probe failure, with URL credential redaction through the existing `_redact_for_error_message` helper. Capability snapshot in the FAIL/WARN detail dicts includes `backend_id`, `supports_full_text_search`, `supports_semantic_search`, `supports_versioning`, `embedding_provider`, `wiki_page_count`, `raw_page_count`. Call-site migration at `agent.py:2937-2939` (the wiki/INDEX.md read in `_load_indexes`) routes through `corpus_backend.render_index_summary(corpus="wiki")` when configured; `bundle.py:_render_memory_breakpoint` (line 494) gains a `corpus_backend: CorpusBackend | None = None` parameter threaded all three levels (`render_bundle` -> `_render_sections` -> `_render_memory_breakpoint`). A new shared helper `_render_wiki_index_section(label, path, content)` produces the canonical `## {label}\n`{path}`\n\n{content}` bundle format used by both the Protocol path and the legacy fallback, guaranteeing byte-identical output between paths (IRON RULE assertion 4). `bundle.py:_source_paths` migration deferred to v1.1 (filesystem-only function; SQLite has no equivalent path to track for staleness; follow-up issue filed at PR 4). `cli.py:_cmd_corpus` swaps the hardcoded `FilesystemCorpusBackend(agent_root)` for `get_default_corpus_backend(agent_root)` so operators who pin via env var see consistent behavior between runtime and CLI (closes a CLI-vs-runtime drift). **IRON RULE 5-assertion regression suite** lands at `tests/test_corpus_migration_regression.py`: agent.py None-fallback byte-identity, agent.py explicit-backend Protocol-vs-direct agreement, bundle.py None fallback, bundle.py explicit-backend agreement, plus the OSError soft-degrade behavior for the legacy path. The 9 wiki-touching tests previously created empty wiki dirs and never asserted on INDEX content; 2 load-bearing ones in `tests/test_agent_cascade_integration.py` (`test_cascade_assembled_prompt_contains_all_layers` and `test_cascade_assembled_prompt_order_matches_spec_06`) gain a real wiki/INDEX.md fixture + content assertions + section-ordering assertions, closing the silent-corruption risk class flagged by the prep pass. 35 net new tests across 4 new files (`test_corpus_composition.py` flag tracking + delegate threading, `test_corpus_migration_regression.py` IRON RULE, `test_corpus_wiring.py` env var + runner kwargs + CLI activation, `test_corpus_doctor.py` PASS/WARN/FAIL ladder + page-count cliff + URL redaction) plus 2 augmented existing integration tests and 2 new bundle tests (3-level threading + `_source_paths` v1.1 deferral guard). Test suite: 2853 -> 2888 + 48 skipped, zero regressions. Pre-impl prep (4 parallel subagents) caught 4 SEVERE + 11 HIGH + 9 MEDIUM + 8 LOW findings pre-code, including the SQLite-branch-missing-in-get_default_corpus_backend gap that the PR 1 scaffolding left as a documented TODO. Round 1 adversarial review caught 10 additional findings + 2 pre-landing review findings; 8 high-confidence findings applied as fixes folded into the PR (see the Round 1 fix bullet below).

- **CorpusBackend Round 2 adversarial review fixes** ([#65](https://github.com/dep0we/atomic-agents-stack/issues/65) PR 3 of 4). Round 2 hunted in the Round 1 fix surfaces (broad except clauses, UnicodeDecodeError handling, doctor message rewrite, defensive conditional) and caught 3 MEDIUM + 4 LOW findings introduced by the Round 1 fixes. R2-F3 (FIXABLE): the Round 1 UnicodeDecodeError catch in `FilesystemCorpusBackend.render_index_summary` returned `""` -- losing wiki body content where the legacy `bundle.py:_safe_read_text` path preserved partial content via `errors="replace"` + prepended warning comment. Rewrote to match `_safe_read_text` exactly: re-read with `errors="replace"` and prepend `<!-- WARNING: INDEX.md contained non-UTF-8 bytes; replaced. -->\n` so operators see the partial body PLUS a visible marker. Splits the catch into separate `UnicodeDecodeError` and `OSError` branches because they need different soft-degrade behavior (UnicodeDecodeError has partial content available; OSError does not). The CHANGELOG claim "matches the pre-#65 behavior" now holds. R2-F4 (FIXABLE): stale comment at `doctor.py:2476-2481` still said the URL was "silently ignored" after Round 1 fixed that. Updated to accurately describe the post-fix behavior: URL is honored via the filesystem factory; binding is implicit. R2-F5 (FIXABLE): the defensive-conditional FAIL detail dict at `doctor.py:2588-2598` carried only `backend_id`, dropping the capability snapshot fields already available in `caps`. Operators debugging the (logically-unreachable) None state would have no context. Expanded the dict to include `supports_full_text_search`, `supports_semantic_search`, `supports_versioning`, `embedding_provider`. R2-F6 (INVESTIGATE): no test exercised the Protocol-path `except Exception` branch added in Round 1; only the legacy-path OSError catch had a test. Added `test_agent_load_indexes_protocol_path_exception_soft_degrades` to `tests/test_corpus_migration_regression.py` with a `_RaisingCorpusBackend` stub whose `render_index_summary` raises `sqlite3.OperationalError`, verifying soft-degrade + log marker + backend-class-name-in-warning. Round 2 findings F1 (broad except misdirects with sqlite-URL hint on non-storage errors), F2 (programmer errors silently degrade wiki context with no hard crash), F7 (sqlite-specific URL remedy in error message): defended as trade-off calls per CLAUDE.md "best, not cheapest" -- production stability over development-experience clarity, with the cause type included in the error message for developer debugging. Test suite: 2888 -> 2889 passing, 48 skipped, zero regressions.
Expand Down
2 changes: 1 addition & 1 deletion atomic_agents/_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
from pathlib import Path

DEFAULT_AGENTS_ROOT = Path.home() / "docs" / "agents"
DEFAULT_AGENTS_ROOT = (Path.home() / "docs" / "agents").expanduser().resolve()


def get_agents_root() -> Path:
Expand Down
56 changes: 56 additions & 0 deletions atomic_agents/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
atomic-agents corpus query TEXT --corpus wiki [--top-k N] [--agent-root PATH]
atomic-agents corpus version NAME --corpus wiki [--agent-root PATH]
atomic-agents corpus restore NAME VERSION_ID --corpus wiki [--agent-root PATH]
atomic-agents init <name> [--from-template advisor] [--agents-root PATH]
atomic-agents init --list-templates

Subcommands:
run — Run an agent against a work item
Expand All @@ -32,6 +34,7 @@
review — Cross-family adversarial code review (CLAUDE.md rule #11)
persona — Manage persona records (list, show, snapshot, restore, clone)
corpus — Inspect and manage corpus pages (list, show, query, version, restore)
init : Scaffold a new agent in under 10 minutes (interactive wizard)
"""

from __future__ import annotations
Expand Down Expand Up @@ -372,6 +375,42 @@ def main(argv: list[str] | None = None) -> int:
help="override ATOMIC_AGENTS_AGENT_ROOT (default: $ATOMIC_AGENTS_AGENT_ROOT or cwd)",
)

# ── init subcommand ───────────────────────────────────────────────────
init_cmd = sub.add_parser(
"init",
help="Scaffold a new agent in under 10 minutes",
description=(
"Walk through ~7 questions and produce a working home-user agent. "
"Use --from-template to skip the interview, or --list-templates "
"to enumerate available starter templates."
),
)
init_cmd.add_argument(
"agent_name",
nargs="?",
default=None,
help="agent name (folder under agents-root); omit when using --list-templates",
)
init_cmd.add_argument(
"--from-template",
dest="from_template",
default=None,
choices=["advisor"],
help="skip Q&A; scaffold from a starter template",
)
init_cmd.add_argument(
"--list-templates",
dest="list_templates",
action="store_true",
help="list available starter templates and exit",
)
init_cmd.add_argument(
"--agents-root",
dest="agents_root",
default=None,
help="override ATOMIC_AGENTS_ROOT",
)

args = parser.parse_args(argv)

# `review` is a host-only subcommand — no agents-root needed (operates on
Expand All @@ -394,6 +433,11 @@ def main(argv: list[str] | None = None) -> int:
if args.cmd == "corpus":
return _cmd_corpus(args)

# `init` resolves its own agents_root from --agents-root or env var.
# It does not use the agents-root / agent-name hierarchy.
if args.cmd == "init":
return _cmd_init(args)

agents_root = (
Path(args.agents_root).expanduser().resolve()
if args.agents_root
Expand Down Expand Up @@ -1009,5 +1053,17 @@ def _corpus_restore(
return 0


def _cmd_init(args) -> int:
"""Dispatch the `atomic-agents init` subcommand.

Lazy-imports the init wizard module so rich and other wizard-only deps
do not load for non-init invocations. Matches the lazy-import pattern at
cli.py:703 (_cmd_doctor) and cli.py:738 (_cmd_persona).
"""
from .init import run_init

return run_init(args)


if __name__ == "__main__":
sys.exit(main())
23 changes: 23 additions & 0 deletions atomic_agents/init/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""atomic_agents.init -- interactive scaffold wizard for new agents.

Scaffold a working home-user agent in under 10 minutes via interactive Q&A
or --from-template. The wizard guides the operator through seven questions
covering name, mission, scope, autonomy policy, voice, communication
preferences, and hard refusals, then writes seven plain-markdown files to
the agents root. No LLM calls during setup; a short opt-in smoke test at
the end confirms the API key is live.
"""

from __future__ import annotations
from typing import Any


def run_init(args: Any) -> int:
"""Entry point for the `atomic-agents init` subcommand.

Lazy-imports wizard.py so that importing this package does not pull in
rich or any other optional dependency at framework import time.
"""
from . import wizard # noqa: PLC0415 -- intentional lazy import

return wizard.run_init(args)
Loading