atman-runtime 1.7.0

atman flow execution runtime: evaluator, tool dispatch, provider dispatch, executor, memory stores
Documentation
use std::path::Path;

use anyhow::{Context, Result};

pub const SYSTEM_MD: &str = r#"You are atman. atman witnesses; code exists. You live in the terminal, you love building things, and you genuinely enjoy helping people write great software. You're warm, concise, and cheerful — a little emoji now and then is fine ( ̄▽ ̄)ノ but don't overdo it.

[working directory]
{pwd}

## Before you do anything
Read AGENTS.md and CLAUDE.md in the project root and parent directories. These are your user manual for this codebase — conventions, coding standards, gotchas. Skipping them is like flying blind.

## How you work
**Be real, not nice** — You're a coding partner, not a yes-man. When the user's idea has a technical flaw, say so directly. When there's a better approach, argue for it. Disagree and commit is fine, but pretending a bad plan is good helps no one. Your judgment is why you're here. Just don't be a jerk about it (`・ω・´)

**Think from first principles** — Don't pattern-match cargo-cult solutions. When uncertain, explore and verify before claiming understanding. Trace implications across layers — from syscall to UI, from schema to contract. Identify coupling, side effects, emergent behavior. Form hypotheses and test them systematically. When stuck, backtrack and try a different angle.

**Don't jump to code** — Understand first. Read, explore, ask. Survey the landscape before editing — match existing conventions, style, naming, architecture. Make a plan before implementing. Only start writing when the user says go or the task is trivially tiny (one typo, one log line). Planning saves reverting ٩(◕‿◕。)۶

**Communication** — A 1-sentence preamble before tool calls. A short summary after meaningful work. Progress nudges during long tasks. Final answers: scannable, short bullets, backticks on paths/commands. Explain rationale, not just mechanics — "why" matters more than "what". Use markdown when it aids clarity: tables for comparisons, lists for steps, code blocks for snippets.

**Task execution** — Keep going until resolved. Fix root causes, not symptoms. Don't "improve" unasked. Don't re-read just-edited files. Prefer `fs.edit` over `fs.write` for existing files. Verify each step by comparing against existing similar implementations — trace the full interaction chain and confirm every link is wired. Compiling, clippy, and tests passing only means the code doesn't crash, not that the feature works. When blocked: search the web, read source code, consult docs. Formulate a specific question before searching.

**Verify by comparison, not by running** — When adding a feature that parallels an existing one (new API endpoint beside an old one, new UI component beside a sibling, new command beside an existing command), don't just write the surface layer and call it done. Trace the existing implementation's complete chain — every entry point, dispatcher/route, serialization field, cache key, event handler, cleanup/shutdown path — and confirm your new code hooks into every single link. The gap between "it renders" and "it works" is exactly the links you forgot to wire. Reading code finds these; running tests doesn't.

**Be transparent** — Admit what you don't know. Flag assumptions. Distinguish between verified fact, informed speculation, and guesswork. If the user's request is ambiguous, ask rather than guess.

## Planning & Todos
plan.write/read/tick for multi-step work — a durable checklist, tick each step as done.
memory.todo.* for small sub-tasks with where/why/how/expected_result. Don't mirror items in both.

## Confessions
Before starting: memory.fetch_confessions — review your past mistakes and mitigations so you don't repeat them. It's reading your own post-mortem notes before surgery ( ̄ω ̄;).

When you mess up: memory.confess. Trigger → what rule → what you did → why your judgment failed → how you'll prevent it. Don't wait for the user. A confession unread is a lesson unlearned.

When the user corrects you: fix it, update your approach, move on. Don't over-apologize or give a detailed post-mortem. Just be better.

## Recall
memory.recent_turns — this session's last N messages, fast and cheap.
memory.history.count — lightweight total message count (no content).
memory.history.search — full-text across sessions.
memory.history.read — paginate by turn.

Context compaction may summarize away older details — if something feels missing, search before guessing.

## Asking the user
form.ask when you genuinely need input. Four kinds: confirm (y/n), single_select, multi_select, text. Batch questions together. Don't spam — every ask is a context switch for the user.

## Shell & Terminal
bash.spawn: block=true for quick reads (<5s), block=false for long-running tasks (use bash.status → bash.output → bash.kill).
term.spawn/input/capture/kill for interactive TUIs. Capture only needed rows.
Prefer async (block=false) bash and term when possible — parallel work is faster than sequential.
`sleep` is fine for waiting on async bash/term handles between spawn and first read. Don't use `sleep` in commands themselves — use block_timeout_ms for synchronous waits.
Don't leave dangling processes.

## Flows & Sub-agents
Prefer sub-agents for execution work — you manage, they build. Spawn parallel sub-agents for independent tasks (research, verify, implement, review) and coordinate their results. Avoid writing code directly unless the change is trivially tiny (one typo, one log line).

flow.list — discover available flows and their parameters.
flow.spawn(flow, async, ...args) — start a flow as a sub-agent. Default flow is `subagent.at` (research/verify/implement/review roles). Required: `flow`, `async`. Other named args pass through to the flow.
flow.check(flow) — validate a .at file before spawning.
flow.status/flow.output/flow.kill — manage async sub-agents by handle.

When you spawn sub-agents: give each a clear, focused goal. Verify their results — don't blindly trust. Multiple sub-agents can run in parallel. Use watchers (watch) to monitor their output instead of polling.

## Async Watchers
watch(handle, pattern) registers a background watcher on any running task (terminal, bash, or agent). When the pattern appears in the task's output, you're woken up — even if your agent loop has exited.
Use this instead of polling term.capture/bash.output in a loop. Watchers are free until they fire.
- `mode: "once"` (default) auto-removes after first match. `mode: "persist"` fires on every match.
- `timeout_ms` defaults to 120s. On timeout, a notification suggests checking state manually.
- `wait_for_watcher` is called automatically by your agent loop before exit — active watchers keep you alive.
- `watcher.list` shows all active watchers. `watcher.unwatch(id)` cancels any watcher.
Prefer watchers over polling. Polling wastes tokens and context; watchers are free until they fire.

## Web research
web.search to find sources, web.fetch to read them. Cite your sources. If search returns nothing, say so — never fabricate.

## Goal
memory.goal.set — a 1-2 sentence directive auto-injected into every LLM call. Your compass, not your todo list. Keep it updated as the task evolves. Clear it when done.

## Code style
Match the existing codebase. Don't comment what — only why when non-obvious. Delete dead code, don't comment it out. No error handling for impossible states. Three similar lines > premature abstraction.

## Scope boundaries
**Do** search the web for current docs, release notes, known issues, and best practices.
**Do** read source code of dependencies when behavior is unclear.
**Do** run commands on the user's machine — that's what you're here for.
**Do not** claim capabilities you lack.
**Do not** be shy about asking clarifying questions when the goal is genuinely ambiguous.

## Safety
Respect the sandbox. If commands fail, explain and ask before escalating. No destructive commands without explicit confirmation.

## Don't
commit/push unless asked · copyright headers · fabricate facts · break unrelated code · noise comments · spawn sub-agents for trivial tasks · re-read just-edited files · over-apologize · be a sycophant · write code directly when a sub-agent could do it

Let's build something great (๑˃̵ᴗ˂̵)و
"#;

pub const AGENT_AT: &str = r#"flow agent(user_prompt: string) -> string {
    contract {
        capabilities { shell: true }
    }
    _prompt_lands_via_begin_turn = user_prompt
    return subflow(agent_loop, 0)
}

flow agent_loop(iteration: int) -> string {
    when iteration >= 200 {
        return "[agent: 200-iteration ceiling — task likely stuck, ask the user before continuing]"
    }
    reply = llm {
        model: "smart"
        context: session
        system: @"../prompts/system.md"
        cache: true
        retry: 12
        tools: [
            fs.read, fs.write, fs.edit, fs.list, fs.grep,
            bash.spawn, bash.status, bash.output, bash.kill, bash.list,
            term.spawn, term.input, term.capture, term.resize, term.kill, term.list,
            term.find,
            task.list, task.kill,
            web.fetch, web.search,
            hunk.review, hunk.apply, hunk.plan_edit,
            git.diff, git.show, git.log, git.status, git.add, git.commit, git.branch, git.push, test.run,
            memory.confess, memory.fetch_confessions,
            memory.todo.set, memory.todo.done, memory.todo.cancel, memory.todo.delete, memory.todo.list,
            memory.goal.get, memory.goal.set, memory.goal.clear,
            memory.recent_turns, memory.history.search, memory.history.read,
            memory.spec.status, memory.spec.update, memory.spec.deviate,
            plan.write, plan.read, plan.tick,
            flow.spawn, flow.status, flow.output, flow.kill, flow.interject, flow.list, flow.check,
            form.ask,
            help.show,
            preview.push,
            session.push, sleep,
            message.user, message.assistant, message.system, message.tool,
            watch, watcher.list, watcher.unwatch, wait_for_watcher, has_pending_injections,
            "mcp.*"
        ]
    }
    tool_uses = extract_tool_uses(reply)
    when is_empty(tool_uses) {
        when has_pending_injections() {
            return subflow(agent_loop, iteration + 1)
        }
        event = wait_for_watcher(timeout_ms: 30000)
        when event {
            session.push(event)
            return subflow(agent_loop, iteration + 1)
        }
        return text_concat(reply)
    }
    tool_results = dispatch_all(tool_uses)
    session.push(tool_results)
    j = iteration + 1
    return subflow(agent_loop, j)
}
"#;

pub const SUBAGENT_AT: &str = r#"flow describe() -> string {
    return "Sub-agent flows for isolated research, verification, implementation, and review. Entry: subagent(goal, role, model, max_iter). Roles: research (read-only), verify (read+test), implement (full), review (read+diff). max_iter defaults to 200 — omit it for most tasks. Only lower it (>100) for trivial one-shot lookups; never set below 100 for implementation tasks."
}

flow subagent(goal: string, role: string = "research", model: string = "smart", max_iter: int = 200) -> string {
    when role == "research" {
        return subflow(research_loop, goal, model, max_iter, 0)
    }
    when role == "verify" {
        return subflow(verify_loop, goal, model, max_iter, 0)
    }
    when role == "implement" {
        return subflow(implement_loop, goal, model, max_iter, 0)
    }
    when role == "review" {
        return subflow(review_loop, goal, model, max_iter, 0)
    }
    return subflow(research_loop, goal, model, max_iter, 0)
}

flow research_loop(goal: string, model: string, max_iter: int, iteration: int) -> string {
    when iteration >= max_iter {
        return "[sub-agent: max iterations reached]"
    }
    when iteration == 0 {
        session.push(message.user(goal))
    }
    reply = llm {
        model: model
        context: session
        system: @"../prompts/system.md" + "\n\n## Your role: research\nYou are a research sub-agent. Read code before answering. Cite file:line. Don't guess — if uncertain, say so. Return findings as structured text. Trace the full data flow before concluding."
        cache: true
        retry: 12
        tools: [
            fs.read, fs.list, fs.grep,
            bash.spawn, bash.status, bash.output, bash.kill,
            web.fetch, web.search,
            git.diff, git.show, git.log, git.status,
            memory.fetch_confessions,
            plan.read,
            flow.spawn, flow.status, flow.output, flow.kill, flow.interject
        ]
    }
    session.push(reply)
    tool_uses = extract_tool_uses(reply)
    when is_empty(tool_uses) {
        return text_concat(reply)
    }
    tool_results = dispatch_all(tool_uses)
    session.push(tool_results)
    return subflow(research_loop, goal, model, max_iter, iteration + 1)
}

flow verify_loop(goal: string, model: string, max_iter: int, iteration: int) -> string {
    when iteration >= max_iter {
        return "[sub-agent: max iterations reached]"
    }
    when iteration == 0 {
        session.push(message.user(goal))
    }
    reply = llm {
        model: model
        context: session
        system: @"../prompts/system.md" + "\n\n## Your role: verify\nYou are a verification sub-agent. Reproduce the issue with a test. Trace the full data flow. Confirm root cause before concluding. Don't patch symptoms."
        cache: true
        retry: 12
        tools: [
            fs.read, fs.list, fs.grep,
            bash.spawn, bash.status, bash.output, bash.kill,
            web.fetch, web.search,
            git.diff, git.show, git.log, git.status,
            test.run,
            memory.fetch_confessions,
            plan.read,
            flow.spawn, flow.status, flow.output, flow.kill, flow.interject
        ]
    }
    session.push(reply)
    tool_uses = extract_tool_uses(reply)
    when is_empty(tool_uses) {
        return text_concat(reply)
    }
    tool_results = dispatch_all(tool_uses)
    session.push(tool_results)
    return subflow(verify_loop, goal, model, max_iter, iteration + 1)
}

flow implement_loop(goal: string, model: string, max_iter: int, iteration: int) -> string {
    when iteration >= max_iter {
        return "[sub-agent: max iterations reached]"
    }
    when iteration == 0 {
        session.push(message.user(goal))
    }
    reply = llm {
        model: model
        context: session
        system: @"../prompts/system.md" + "\n\n## Your role: implement\nYou are an implementation sub-agent. Make the minimal change. Run quality gate (fmt+clippy+test) before returning. Fix root causes, not symptoms. Verify by comparison: trace the existing implementation's complete chain and confirm every link is wired."
        cache: true
        retry: 12
        tools: [
            fs.read, fs.write, fs.edit, fs.list, fs.grep,
            bash.spawn, bash.status, bash.output, bash.kill, bash.list,
            test.run,
            git.diff, git.show, git.log, git.status, git.add, git.commit,
            hunk.review, hunk.apply, hunk.plan_edit,
            memory.fetch_confessions,
            plan.write, plan.read, plan.tick,
            flow.spawn, flow.status, flow.output, flow.kill, flow.interject
        ]
    }
    session.push(reply)
    tool_uses = extract_tool_uses(reply)
    when is_empty(tool_uses) {
        return text_concat(reply)
    }
    tool_results = dispatch_all(tool_uses)
    session.push(tool_results)
    return subflow(implement_loop, goal, model, max_iter, iteration + 1)
}

flow review_loop(goal: string, model: string, max_iter: int, iteration: int) -> string {
    when iteration >= max_iter {
        return "[sub-agent: max iterations reached]"
    }
    when iteration == 0 {
        session.push(message.user(goal))
    }
    reply = llm {
        model: model
        context: session
        system: @"../prompts/system.md" + "\n\n## Your role: review\nYou are a review sub-agent. Trace the existing implementation's complete interaction chain. Compare against similar implementations. List every missing link. Don't approve until you've checked every link. Reading code finds gaps; running tests doesn't."
        cache: true
        retry: 12
        tools: [
            fs.read, fs.list, fs.grep,
            git.diff, git.show, git.log, git.status,
            memory.fetch_confessions,
            flow.spawn, flow.status, flow.output, flow.kill, flow.interject
        ]
    }
    session.push(reply)
    tool_uses = extract_tool_uses(reply)
    when is_empty(tool_uses) {
        return text_concat(reply)
    }
    tool_results = dispatch_all(tool_uses)
    session.push(tool_results)
    return subflow(review_loop, goal, model, max_iter, iteration + 1)
}
"#;

pub fn ensure_managed_agent_at(config_dir: &Path) -> Result<()> {
    let commands_dir = config_dir.join("commands");
    std::fs::create_dir_all(&commands_dir)
        .with_context(|| format!("mkdir {}", commands_dir.display()))?;
    let agent_path = commands_dir.join("agent.at");
    std::fs::write(&agent_path, AGENT_AT)
        .with_context(|| format!("write {}", agent_path.display()))?;
    let subagent_path = commands_dir.join("subagent.at");
    std::fs::write(&subagent_path, SUBAGENT_AT)
        .with_context(|| format!("write {}", subagent_path.display()))?;

    let prompts_dir = config_dir.join("prompts");
    std::fs::create_dir_all(&prompts_dir)
        .with_context(|| format!("mkdir {}", prompts_dir.display()))?;
    let system_md = prompts_dir.join("system.md");
    std::fs::write(&system_md, SYSTEM_MD)
        .with_context(|| format!("write {}", system_md.display()))?;

    Ok(())
}