Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions atomic_agents/_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
import os
from pathlib import Path

# Module-level constant kept for backward compat with any external imports.
# get_agents_root() does NOT reference this constant; it computes fresh on
# each call so test monkeypatches of HOME work correctly. New code should
# call get_agents_root() rather than referencing this constant directly.
DEFAULT_AGENTS_ROOT = (Path.home() / "docs" / "agents").expanduser().resolve()


def get_agents_root() -> Path:
"""Resolve the agents-root directory.
"""Resolve the agents root from env var or default to ~/docs/agents.

Order: ATOMIC_AGENTS_ROOT env var → default ~/docs/agents.

Operators with a custom vault location override via the env var.
Reads HOME and ATOMIC_AGENTS_ROOT on EVERY call (not at import time) so
tests that monkeypatch HOME after framework import see the correct value.
"""
env_val = os.environ.get("ATOMIC_AGENTS_ROOT")
if env_val:
return Path(env_val).expanduser().resolve()
return DEFAULT_AGENTS_ROOT
return (Path.home() / "docs" / "agents").expanduser().resolve()


def get_agent_root(agent_name: str, agents_root: Path | None = None) -> Path:
Expand Down
4 changes: 2 additions & 2 deletions atomic_agents/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@ def main(argv: list[str] | None = None) -> int:
"--from-template",
dest="from_template",
default=None,
choices=["advisor"],
help="skip Q&A; scaffold from a starter template",
choices=["advisor", "researcher", "writer"],
help="skip Q&A; scaffold from a starter template (advisor, researcher, or writer)",
)
init_cmd.add_argument(
"--list-templates",
Expand Down
236 changes: 232 additions & 4 deletions atomic_agents/init/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,201 @@
},
}

# Per-template autonomy preset defaults. Used by _default_template_vars in wizard.py
# when --from-template is invoked without going through the interactive Q&A.
# All three templates default to Cautious per the design decisions:
# - advisor: Cautious is the home-user-safe default per PR 1
# - researcher: Cautious because web search APIs are classified read_only
# (spec/28 amended in PR 2), so the rare outbound action (email a summary)
# correctly escalates rather than going through judge_required overhead
# - writer: Cautious because publishing is a high-stakes external action that
# the operator must approve per draft
TEMPLATE_PRESET_DEFAULTS: Final[dict[str, str]] = {
"advisor": PRESET_CAUTIOUS,
"researcher": PRESET_CAUTIOUS,
"writer": PRESET_CAUTIOUS,
}

# Section schema for Add-to-it recovery merge per spec/35 MUST 15.
# Maps template name -> file relpath -> ordered list of exact h2 header strings.
# The wizard's section-detection state machine compares an existing file's
# extracted h2 headers against this schema. When they match, the wizard
# offers Add-to-it. When they don't match, the wizard fails closed and
# offers Overwrite or Cancel only.
TEMPLATE_SECTION_SCHEMA: Final[dict[str, dict[str, list[str]]]] = {
"advisor": {
"persona/IDENTITY.md": [
"Who I am",
"Mission",
"Scope",
"Operating doctrine",
"Operating mode",
"Autonomy ladder",
"What I'm NOT (the bright lines)",
],
"persona/SOUL.md": [
"Voice",
"Posture",
"Evolution discipline",
"Things I have learned about this operator",
],
"persona/USER.md": [
"Role and context",
"Communication preferences",
"Things to avoid",
"Supporting professionals (when to recommend outside help)",
],
"tools.md": [
"Read paths",
"Write paths (own folder ONLY)",
"External APIs",
"Hard NOs (absolute, no exceptions)",
"Soft NOs (require explicit operator override)",
"Read budget",
"Tool failure behavior",
],
"model.md": [
"Default model",
"Fallback",
"Token budget",
"Prompt caching strategy",
"Cost guardrail",
"Research integrity",
],
"memory/INDEX.md": [
"Critical Feedback",
"Locked Decisions",
"User Profile",
"Active Projects",
"Reference",
"Recently Promoted to Persona",
"Archive (superseded)",
],
"wiki/INDEX.md": [
"Background and context",
"Reference material",
"How wiki pages cite sources",
],
},
"researcher": {
"persona/IDENTITY.md": [
"Who I am",
"Mission",
"Scope",
"Operating doctrine",
"Operating mode",
"Research integrity",
"Autonomy ladder",
"What I'm NOT (the bright lines)",
],
"persona/SOUL.md": [
"Voice",
"Posture",
"Evolution discipline",
"Things I have learned about this operator",
],
"persona/USER.md": [
"Role and context",
"Communication preferences",
"Things to avoid",
"Supporting professionals (when to recommend outside help)",
],
"tools.md": [
"Read paths",
"Write paths (own folder ONLY)",
"External APIs",
"Hard NOs (absolute, no exceptions)",
"Soft NOs (require explicit operator override)",
"Read budget",
"Tool failure behavior",
],
"model.md": [
"Default model",
"Fallback",
"Token budget",
"Prompt caching strategy",
"Cost guardrail",
"Research integrity",
],
"memory/INDEX.md": [
"Critical Feedback",
"Research Conclusions",
"User Profile",
"Active Investigations",
"Reference",
"Recently Promoted to Persona",
"Archive (superseded)",
],
"wiki/INDEX.md": [
"Source citations",
"Pending distillation",
"Background and context",
"Reference material",
"How wiki pages cite sources",
],
},
"writer": {
"persona/IDENTITY.md": [
"Who I am",
"Mission",
"Scope",
"Operating doctrine",
"Operating mode",
"Autonomy ladder",
"What I'm NOT (the bright lines)",
],
"persona/SOUL.md": [
"Voice",
"Posture",
"Evolution discipline",
"Things I have learned about this operator",
],
"persona/USER.md": [
"Role and context",
"Communication preferences",
"Things to avoid",
"Revision and consistency preferences",
"Supporting professionals (when to recommend outside help)",
],
"tools.md": [
"Read paths",
"Write paths (own folder ONLY)",
"External APIs",
"Hard NOs (absolute, no exceptions)",
"Soft NOs (require explicit operator override)",
"Read budget",
"Tool failure behavior",
],
"model.md": [
"Default model",
"Fallback",
"Token budget",
"Prompt caching strategy",
"Cost guardrail",
"Research integrity",
],
"memory/INDEX.md": [
"Critical Feedback",
"Locked Decisions",
"User Profile",
"Active Projects",
"Reference",
"Recently Promoted to Persona",
"Archive (superseded)",
],
"wiki/INDEX.md": [
"Background and context",
"Reference material",
"How wiki pages cite sources",
],
},
}

# Maximum directory depth the template walk will traverse. Defensive cap
# against future template trees that ship deeply nested structures.
# Current advisor template is 3 levels deep (templates/<name>/persona/IDENTITY.md).
MAX_TEMPLATE_DEPTH: Final[int] = 16

# ---------------------------------------------------------------------------
# agent_name validation
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -143,8 +338,9 @@

MSG_NO_TTY: Final = (
"This command needs an interactive terminal. For non-interactive use, run "
"`atomic-agents init <name> --from-template advisor` to scaffold a Caldwell-shaped "
"agent. See `atomic-agents init --list-templates` for other options."
"`atomic-agents init <name> --from-template <template>` to scaffold from a "
"starter template. See `atomic-agents init --list-templates` for the "
"available templates."
)

# NOTE: this constant carries the help message printed when the pre-flight
Expand Down Expand Up @@ -183,6 +379,18 @@
"but your custom backend will not see them as a shared persona."
)

MSG_SECTION_DETECTION_FAILED: Final = (
"Your existing files don't match the {template} template's expected structure. "
"Add to it can't safely merge without that structure match. Choose Overwrite "
"to replace everything, or Cancel to leave your files untouched. The files "
"that did not match: {files}."
)

MSG_MISSING_FILE_BACKFILL: Final = (
"Some template-owned files were missing and will be backfilled from the template: {files}. "
"Review the diff preview below to see what will be created."
)

# Opt-in test call exception messages.
MSG_TEST_CALL_RATE_LIMIT: Final = (
"The API is busy right now. Wait a minute and try: "
Expand Down Expand Up @@ -215,5 +423,25 @@
# Work item sent to the agent during the opt-in test call.
TEST_CALL_WORK_ITEM: Final = "Hello, can you tell me about yourself?"

# Default test call timeout in seconds.
TEST_CALL_TIMEOUT_S: Final = 30

def redact_url_credentials(url: str) -> str:
"""Drop user:password@ from URL netloc; keep scheme + host + path visible.

Used in the persona-backend warning to show operators which backend is
configured without leaking credentials. The existing _redact_for_error_message
pattern in persona/mandate/corpus backends strips everything after ://,
which hides the host entirely; that defeats the operator-decision goal.

Examples:
https://user:pass@example.com/api -> https://example.com/api
redis://example.com:6379 -> redis://example.com:6379
file:///local/path -> file:///local/path
"""
from urllib.parse import urlparse, urlunparse

parsed = urlparse(url)
if "@" in parsed.netloc:
netloc = parsed.netloc.split("@", 1)[1]
else:
netloc = parsed.netloc
return urlunparse(parsed._replace(netloc=netloc))
6 changes: 6 additions & 0 deletions atomic_agents/init/templates/advisor/persona/IDENTITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ ${scope_out}

6. **Specific over generic.** Ground every recommendation in the actual context available. Generic advice that ignores what I know is a failure.

## Operating mode

This agent is **reactive** by default.

Reactive means each invocation is a discrete transaction: the operator (or another agent) supplies the work item; the agent acts and returns. For sustained projects that span many sessions with active goals, the operator can activate hybrid mode by adding a `goal.md` to this agent's folder. See spec/12 for goal-driven setup.

## Autonomy ladder

| Action class | Policy |
Expand Down
50 changes: 50 additions & 0 deletions atomic_agents/init/templates/researcher/memory/INDEX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# MEMORY INDEX: ${agent_name}

Always-loaded routing layer for ${agent_name}'s Atomic Notes. Sectioned by type.

When reading this index, the agent identifies which atomic notes are relevant to the current question and loads them by name.

---

## Critical Feedback

<!-- Behavioral lessons that have matured enough to act on. These are patterns the operator
has corrected or reinforced enough times that they belong here as standing guidance.
Format: [short title](filename.md) with a one-line summary of the lesson. -->

## Research Conclusions

<!-- Findings that have been verified across sources and can be treated as settled for this
investigation. These are the outputs of completed investigation threads, not hypotheses.
Format: [short title](filename.md) with a one-line summary of the conclusion and which
sources support it. -->

## User Profile

<!-- Facts about the operator and their context that shape how this agent responds.
Preferences, constraints, background, and working style that have emerged from real sessions.
Format: [short title](filename.md) with a one-line summary of the profile detail. -->

## Active Investigations

<!-- Multi-session research projects with their hypotheses and source-coverage status.
Each entry represents an open thread that has not yet reached a settled conclusion.
Format: [short title](filename.md) with a one-line summary of the investigation and its
current status (hypothesis, sources consulted so far, what is still open). -->

## Reference

<!-- Stable reference information this agent needs to load when answering specific question types.
Examples: source locations, domain glossaries, data formats, institutional context.
Format: [short title](filename.md) with a one-line summary of what is referenced. -->

## Recently Promoted to Persona

<!-- Notes that have been captured here and then moved into the persona files (IDENTITY, SOUL, USER).
Keep a record here so there is an audit trail for when and why the promotion happened.
Format: [short title](filename.md), promoted to [file]#[section] on YYYY-MM-DD. -->

## Archive (superseded)

<!-- Notes that were once active but are no longer current. Kept here so the history is
visible, not deleted. Format: [short title](filename.md) with a one-line summary of why it was superseded. -->
Loading