Skip to content

Latest commit

 

History

History
334 lines (247 loc) · 16 KB

File metadata and controls

334 lines (247 loc) · 16 KB

Python API reference

The main public class is alancode.AlanCodeAgent. This page documents the methods and properties you're likely to use.

For a tutorial-style introduction, see guides/building-agents.md.

Constructor

from alancode import AlanCodeAgent

AlanCodeAgent(
    backend: str | LLMBackend | None = None,
    *,
    model: str | None = None,
    api_key: str | None = None,
    base_url: str | None = None,
    request_timeout: int | str | None = None,
    context_window: int | str | None = None,
    cwd: str | None = None,
    permission_mode: str | None = None,
    max_iterations_per_turn: int | None = None,
    max_output_tokens: int | str | None = None,
    escalated_max_tokens: int | None = None,
    empty_response_retries: int | None = None,
    no_verbalize_warning: bool | None = None,
    persist_thinking: bool | None = None,
    disable_thinking: bool | None = None,
    memory: str | None = None,
    tool_call_format: str | None = None,
    session_id: str | None = None,
    ask_callback: Callable | None = None,
    verbose: bool | None = None,
    extra_tools: list[Tool] | None = None,
    custom_system_prompt: str | None = None,
    append_system_prompt: str | None = None,
    gui_label: str | None = None,
    programmatic: bool = False,
    tools: list[Tool] | None = None,
    disabled_tools: list[str] | None = None,
    **backend_kwargs: Any,
)

All settings omitted (None) fall through to the resumed session snapshot when session_id is supplied, otherwise .alan/settings.json, then built-in defaults. Explicitly supplied declared settings pass through their registered per-key validators. See guides/configuration.md.

Key arguments:

  • cwd — working directory the agent operates in. Defaults to os.getcwd().
  • backend — either a string ("auto", "anthropic-native", "scripted") or a concrete LLMBackend instance. If omitted, it is inferred from model.
  • request_timeout — positive seconds or "auto". A custom base_url gets a 3,600-second automatic timeout for slow local inference.
  • context_window — positive token count or "auto"; also exposed as the resolved agent.context_window property.
  • max_output_tokens - starting per-call output budget. "auto"/None uses the model default capped at one quarter of its context window.
  • escalated_max_tokens - retry budget used after output truncation when it is higher than the resolved starting budget. Set it at or below max_output_tokens for a hard ceiling.
  • empty_response_retries - corrective retries for wholly empty or reasoning-only replies with no visible answer/tool (2 by default; 0 disables).
  • no_verbalize_warning - when a turn calls tools with no visible text, send a <system-reminder> asking the model to narrate (False by default). Not a retry: the tool calls still run and their results are kept.
  • persist_thinking - re-inject reasoning already returned by the backend into subsequent requests and compaction summaries (False by default). It does not enable provider-side thinking.
  • disable_thinking - send chat_template_kwargs={"enable_thinking": false} to ask a server-side chat template to stop emitting reasoning (False by default). LiteLLM (backend="auto") only; the native Anthropic backend ignores it.
  • session_id — if set, resume an existing session; otherwise a new session ID is generated.
  • ask_callbackasync def callback(question: str, options: list[str]) -> str. Called when a tool needs user approval. Return the chosen option text (or any string to use as a free-text answer).
  • extra_tools — additional tools appended to the agent's tool list. See guides/building-agents.md for embedding patterns.
  • custom_system_prompt — full prompt replacement; normal skills, memory, scratchpad, and ALAN.md sections are omitted.
  • append_system_prompt — additive instructions after the normal prompt, or after custom_system_prompt when both are set.
  • gui_label — URL path segment for the GUI bridge. Defaults to the cwd basename.
  • programmatic — when True, runs Alan as a library component rather than a developer assistant. See Programmatic mode below.
  • tools — explicit base tool list, replacing the default builtins. Composes with disabled_tools and extra_tools. See Tool selection below.
  • disabled_tools — list of tool names to remove from the base set (e.g. ["WebFetch", "GitCommit"]).

Query methods

The 2×2 matrix:

Sync Async
Final text only query(prompt) -> str query_async(prompt) -> str
All events query_events(prompt) -> list[StreamEvent | Message] query_events_async(prompt) -> AsyncGenerator[StreamEvent | Message, None]

query(prompt: str) -> str

Run a turn synchronously. Returns the assistant's final text response.

answer = agent.query("Explain the compaction system")

Internally runs asyncio.run, or dispatches to a worker thread if an event loop is already running (Jupyter-safe).

async query_async(prompt: str) -> str

Same as query but awaitable.

answer = await agent.query_async("Explain the compaction system")

query_events(prompt: str) -> list[StreamEvent | Message]

Synchronous; returns a full list of events after the turn completes. Useful for post-hoc inspection.

async query_events_async(prompt: str) -> AsyncGenerator[StreamEvent | Message, None]

The real primitive and the only live-streaming query method. Yields events as they're produced:

async for event in agent.query_events_async("Summarize README.md"):
    # handle each event
    pass

The union aliases and message dataclasses live in alancode.messages.types:

Event When
RequestStartEvent Each API call begins (useful for "Thinking..." indicators).
AssistantMessage with hide_in_api=True Streaming delta — text chunks, thinking chunks.
AssistantMessage with hide_in_api=False Final assembled message after the stream completes. Has tool calls.
UserMessage Injected (interactive system reminders, tool results). The automatic date/time reminder is omitted with programmatic=True.
SystemMessage Informational (compaction markers, etc.).
AttachmentMessage Structured metadata (e.g., max_iterations_per_turn_reached).
ProgressMessage Reserved public/serialization type for long-running updates; the current query/tool path does not construct one.

Filter on hide_in_api to distinguish streaming deltas from final messages — see the streaming example in guides/building-agents.md.

query_async() is asynchronous in the normal Python sense (it does not block the event loop), but it still waits for the whole turn and returns one final string. For live text, iterate query_events_async() and select virtual AssistantMessage text deltas. There is currently no separate query_stream() convenience method.

Consume query_events_async() to exhaustion. A final assembled AssistantMessage is an event within an iteration, not necessarily the end of the turn: usage for that completed call is already recorded, but advancing the generator after the event performs requested tools and reaches later model rounds. If a caller intentionally stops early, it must await stream.aclose(); breaking at the first final assistant can otherwise skip tools and leave cleanup deferred.

State inspection

Property Type Description
agent.session_id str Current session ID (auto-generated or passed in).
agent.messages list[Message] Copy of the current conversation (safe to mutate the returned list).
agent.usage Usage Cumulative tokens across the session.
agent.last_usage Usage Usage from the most recent successful API call.
agent.cost_usd float Cumulative estimated cost.
agent.cost_unknown bool True if the model's pricing isn't known.
agent.cwd str Working directory.
agent.turn_count int Number of user messages processed this session.
agent.context_window int Resolved context-window size after settings/model metadata.

Usage has: input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens, plus a total_input property summing the three input types.

Runtime control

abort()

agent.abort()

Sets the abort event. Alan checks it at loop checkpoints and after streaming; it does not forcibly terminate an in-flight SDK request.

inject_message(text: str)

agent.inject_message("Actually, focus on calc.py only.")

Queues a user message to be delivered at the start of the next iteration. Useful for orchestration frameworks that steer mid-turn.

update_session_setting(key: str, value: Any) -> str | None

error = agent.update_session_setting("permission_mode", "yolo")
if error:
    print("Invalid:", error)

Validates and updates a setting in memory and on disk. Returns an error message string on validation/backend-creation failure, or None on success. backend, model, api_key, base_url, request_timeout, and context_window trigger transactional backend recreation.

Lifecycle

async close()

await agent.close()

Idempotently fires session_end hooks, closes the backend's owned client/server resources, and releases the session lock. Call it when done; the CLI does this on /exit.

Programmatic mode

Use programmatic=True when Alan is being driven by another program (a benchmark harness, a parent agent, an automated pipeline) rather than a developer at a terminal. It detaches Alan from project- and host-level state that's normally helpful for an interactive assistant but contaminates a controlled run.

agent = AlanCodeAgent(
    model="claude-sonnet-4-6",
    cwd="/path/to/experiment",
    permission_mode="yolo",
    programmatic=True,
)

When programmatic=True:

  • ~/.alan/ALAN.md (global instructions) is not loaded.
  • <cwd>/ALAN.md (project instructions) is not loaded.
  • ~/.alan/memory/MEMORY.md (global memory index) is not loaded.
  • The default tool set excludes WebFetch, GitCommit, and AskUserQuestion. SkillTool is also not appended.

Project-scoped state in <cwd>/.alan/sessions/<id>/ (transcript, state, scratchpad) is unchanged — that's the agent's own working memory and is needed for resume.

You can override the curated tool set with tools= or refine it with disabled_tools= (see below).

Tool selection

Three knobs control the agent's tool list, applied in order:

  1. Base set. Resolved from the first of:
    • tools=[...] if passed (explicit replacement),
    • the curated programmatic set if programmatic=True,
    • all enabled built-in tools otherwise (the SkillTool is appended in this case).
  2. Subtract any names listed in disabled_tools.
  3. Append anything in extra_tools.
# Read-only assistant: drop write/exec tools entirely
agent = AlanCodeAgent(disabled_tools=["Bash", "Edit", "Write", "GitCommit"])

# Custom tool list (e.g. for a domain-specific agent)
agent = AlanCodeAgent(tools=[MyDomainTool(), MyOtherTool()])

# Programmatic mode plus an extra custom tool
agent = AlanCodeAgent(programmatic=True, extra_tools=[MyTool()])

Session locking

SessionState takes an exclusive flock on <cwd>/.alan/sessions/<session_id>/session.lock at construction. A second process attempting to open the same session raises alancode.session.SessionLockedError. The lock is released by agent.close() and on process exit.

Distinct session IDs have separate state and transcript files. Project settings, project allow rules, and the context-window cache use lock-backed atomic read-modify-write operations, so concurrent updates do not corrupt JSON or silently lose unrelated entries.

This does not make two coding agents transactionally safe in the same source tree: separate sessions can still edit the same file from stale views. Use a separate Git worktree per concurrent coding agent and merge their changes normally. Within one agent, read-only tool calls may run concurrently while write/exec calls are serialized; separate agents do not share that scheduler.

The remaining process-global behavior is library configuration (for example LiteLLM logger verbosity), not conversation/request state. Each agent owns its backend instance, message list, abort event, session start time, and lifecycle.

Custom permission callbacks

async def my_ask(question: str, options: list[str]) -> str:
    print(f"\n{question}")
    for i, opt in enumerate(options, 1):
        print(f"  {i}) {opt}")
    choice = input("> ").strip()
    if choice.isdigit() and 1 <= int(choice) <= len(options):
        return options[int(choice) - 1]
    return choice  # free-text answer

agent = AlanCodeAgent(ask_callback=my_ask, permission_mode="edit")

The callback is awaited when a tool needs approval. Return one of the option strings to accept the corresponding action (Allow, Deny, Allow always), or return any other string — that string becomes the "tool result" sent back to the model (so the user can deny with a reason in one step).

Ctrl+C from within the callback should raise KeyboardInterrupt → Alan converts it to asyncio.CancelledError → the turn aborts cleanly.

Example: a minimal synchronous script

from alancode import AlanCodeAgent

agent = AlanCodeAgent(
    model="openrouter/google/gemini-2.5-flash",
    permission_mode="yolo",  # auto-approve for automation
)

answer = agent.query("What's 2+2?")
print(answer)

print(f"Cost: ${agent.cost_usd:.4f}")
print(f"Tokens: {agent.usage.total_input} in, {agent.usage.output_tokens} out")

import asyncio
asyncio.run(agent.close())

Example: async streaming

import asyncio
from alancode import AlanCodeAgent
from alancode.messages.types import AssistantMessage, TextBlock, ToolUseBlock

async def main():
    agent = AlanCodeAgent(permission_mode="yolo")
    try:
        async for event in agent.query_events_async("List files and summarize."):
            if not isinstance(event, AssistantMessage):
                continue
            for block in event.content:
                if event.hide_in_api and isinstance(block, TextBlock):
                    print(block.text, end="", flush=True)
                elif not event.hide_in_api and isinstance(block, ToolUseBlock):
                    print(f"\n[tool: {block.name}({block.input})]")
    finally:
        await agent.close()

asyncio.run(main())

Example: injecting a custom backend

from alancode import AlanCodeAgent
from alancode.backends.base import LLMBackend

class MyBackend(LLMBackend):
    async def stream(self, messages, system, tools, *, model, max_tokens, thinking, **kwargs):
        # yield BackendStreamEvent objects
        ...
    def get_model_info(self, model):
        ...
    async def close(self):
        ...

agent = AlanCodeAgent(backend=MyBackend(...))

Remote-scripted backend

AlanCodeAgent(backend="scripted", model="remote", ...) starts an embedded HTTP server and waits for an external caller (a human or another agent) to act as the LLM. Useful for debugging tool wiring, system prompts, and framework integrations without burning tokens.

See guides/remote-scripted-backend.md for the endpoints, payload shapes, and a typical curl loop.

Related