Documentation
SDK reference
aristotlelib is the official Python client for the Aristotle API. Every method that issues a network request is async.
The library reports what it does, such as a task's snapshot or how a wait ended, through the aristotle logger, and configures no logging of its own. To see those messages, configure logging in your program, for example with logging.basicConfig(level=logging.INFO).
import aristotlelib
from aristotlelib import (
Project,
ProjectStatus,
AgentQuestionsSetting,
AgentTask,
TaskStatus,
Event,
EventType,
EventStatus,
Question,
AristotleAPIError,
)
Authentication
The library looks for a key in three places, in this order: one set programmatically, the ARISTOTLE_API_KEY environment variable, and the credentials aristotle login stored. So on a machine you have logged in on there is nothing to do, and a deployment can still hand the process its own key.
# Option 1: environment variable
# export ARISTOTLE_API_KEY='arstl_...'
# Option 2: set programmatically, which overrides the other two
aristotlelib.set_api_key("arstl_...")
aristotle login prints a code that you approve in the browser, on any device, so it works over SSH and in containers too. The code expires after 10 minutes. Logging in again in the browser on the same machine retires the key the last browser login stored there, when the same account and organization approve it. To store a key you already have instead, pipe it in: printf '%s\n' "$KEY" | aristotle login. A key you pipe in is never retired this way.
Project
Project.create(prompt, tar_file_path=None, public_file_path=None, agent_questions_setting=AgentQuestionsSetting.TIMEOUT_15_MIN, name=None)
Submits a new project. tar_file_path is an optional .tar.gz of supplementary files, at most 100 MB; public_file_path sets the filename recorded by Aristotle. name names the project, as aristotle list and the web app show it. By default Aristotle may ask clarifying questions; see AgentQuestionsSetting.
project = await Project.create(
prompt="Prove that every function in ring_buffer.c is memory safe",
tar_file_path="ring-buffer.tar.gz",
)
Project.create_from_directory(prompt, project_dir, agent_questions_setting=AgentQuestionsSetting.TIMEOUT_15_MIN, name=None, *, include_ignored=False)
Submits a new project from a local directory, as one archive of at most 100 MB. It leaves out .git, build output and editor files, files an ignore rule such as a .gitignore entry matches, files named like credentials, result archives an earlier run saved, and files whose names aren't UTF-8. No ignore rule holds back .lake or Lean's build files, such as lakefile.toml and lean-toolchain. Pass include_ignored=True to upload the files the ignore rules match too, such as sources a build generates.
project = await Project.create_from_directory(
prompt="Hunt for real bugs with provable resolution",
project_dir="./my-project",
)
Project.from_id(object_id)
Loads a project by ID. Raises AristotleNotFoundError when no project you can see has that ID.
Project.list_projects(pagination_key=None, limit=30, status=None, latest_run_status=None)
Returns (projects, next_pagination_key), newest first. limit ranges from 1 to 100. status accepts a single ProjectStatus or a list, and filters returned projects to those statuses. latest_run_status takes a list of TaskStatus values, and keeps the projects whose latest task has one of them.
projects, next_key = await Project.list_projects(limit=10)
running, _ = await Project.list_projects(status=ProjectStatus.RUNNING)
project.ask(prompt, files=None, agent_questions_setting=AgentQuestionsSetting.TIMEOUT_15_MIN)
Continues a project. If a task is running, the prompt changes its direction, and the task keeps its own questions setting. If the project is idle, the prompt starts a new task. files can be used only on an idle project.
project.get_tasks(pagination_key=None, limit=10, newest_first=True)
Returns (tasks, next_pagination_key).
project.get_files(destination=None)
Refreshes the project, then downloads the result tarball if available or the original input otherwise. If destination is omitted, the file is downloaded in the current directory; a directory, or a path ending in a separator, gets it under its default name. Raises ProjectHasNoFilesError when the project has neither.
path = await project.get_files(destination="output.tar.gz")
project.refresh()
Refreshes the project's state in place.
Fields
project_id, description (the project's name), status (ProjectStatus), latest_run_status (the TaskStatus of its latest task, or None before the first), created_at, last_updated, has_input, has_files.
AgentTask
AgentTask.from_id(object_id)
Loads a task by ID.
task.wait_for_completion(num_events=3, *, interactive=False, stop_on_questions=False)
Waits until the task reaches a terminal state. By default it writes nothing to stdout and reads nothing from stdin: it logs the questions waiting for an answer, and how the task ended, to the aristotle logger. interactive=True shows live progress on stdout, as the CLI does: the most recent events and the project's property counts. Pass num_events=0 to hide recent events. At an interactive terminal it also asks you Aristotle's questions as they arrive; elsewhere it logs them.
With stop_on_questions=True it returns early with the open questions, as a list of Question: as soon as one is waiting by default or away from a terminal, and with interactive=True at a terminal once it has asked them, if some are still open. A read-only task never returns early. A wait that doesn't return early returns an empty list. A wait also ends if the connection to Aristotle keeps failing, while the task goes on, so inspect task.status after it returns.
You don't need to keep a process waiting to find out when a task ends. Aristotle emails you when a task completes or fails, with a link to the project. Turn these emails off under Email notifications in Settings.
task.get_events(pagination_key=None, limit=50, newest_first=True)
Returns (events, next_pagination_key). Each event represents one meaningful step Aristotle took during the task.
task.show(num_events=0, pagination_key=None)
Logs a snapshot of the task, and its num_events most recent events, to the aristotle logger. Returns the next pagination key if more remain.
task.cancel()
Cancels the task if queued or in progress, and updates it in place. A task already canceled counts as canceled; one that ended any other way raises AristotleAPIError.
task.refresh()
Refreshes the task's state in place.
Fields
agent_task_id, project_id, status (TaskStatus), created_at, last_updated_at, percent_complete (deprecated, always None), description, file_name, output_summary, read_only (True on someone else's shared project, where only the owner can answer questions).
Event
An Event represents one step Aristotle took during a task: a build, a search, a question, a summary, or a message. Build, search, and read events record only that the step happened: their content is empty and their file_path is None. Events are shown by task.wait_for_completion(interactive=True) and task.show, and listed by task.get_events.
event.answer(answer)
Answers an AGENT_QUESTION event whose status is still SENT, the form questions took before Question. Aristotle now asks each question as a Question; answer it with question.answer().
Fields
event_id, agent_task_id, event_type (EventType), created_at, content, file_path, explanation, suggestions, status (EventStatus), duration_seconds.
Question
A question Aristotle asked during a task. Aristotle keeps working after it asks; when it needs the answer to go on, it waits up to 15 minutes, then carries on without it. A question takes an answer until the task ends.
Question.list_for_task(agent_task_id)
Returns every question asked during the task, in any state, oldest first.
question.answer(answer)
Sends the answer and updates the question from Aristotle's reply. Raises QuestionClosed when the question no longer takes an answer.
Fields
question_id, project_id, agent_task_id, content, suggestions, response (the answer, or None), created_at, updated_at. answered is True once it has an answer, and ignored when it closed without one; pending is True otherwise, including after the task ends, when the question no longer takes an answer.
ProjectStatus
class ProjectStatus(IntEnum):
UNKNOWN = 0
RUNNING = 1
IDLE = 2
RUNNING indicates at least one active task. IDLE indicates no task is currently running. UNKNOWN indicates a server response this SDK version does not recognize; upgrade aristotlelib.
AgentQuestionsSetting
class AgentQuestionsSetting(IntEnum):
DISABLED = 1
TIMEOUT_15_MIN = 2
TIMEOUT_15_MIN, the default, lets Aristotle ask clarifying questions while a task runs, whether or not you wait for it, and wait up to 15 minutes for each answer. Use DISABLED for a batch run nobody will answer: Aristotle then verifies what doesn't need a decision only you can make, leaves the rest unverified, and names the open question in its summary, as it does when a question gets no answer.
TaskStatus
class TaskStatus(Enum):
UNKNOWN = "UNKNOWN"
QUEUED = "QUEUED"
IN_PROGRESS = "IN_PROGRESS"
COMPLETE = "COMPLETE"
COMPLETE_WITH_ERRORS = "COMPLETE_WITH_ERRORS"
OUT_OF_BUDGET = "OUT_OF_BUDGET"
FAILED = "FAILED"
CANCELED = "CANCELED"
COMPLETE_WITH_ERRORS signals partial progress that did not fully satisfy the prompt. OUT_OF_BUDGET signals compute exhaustion; partial results are typically available. Both are good candidates for a follow-up with project.ask().
EventType
class EventType(IntEnum):
UNKNOWN = 0
MESSAGE = 1
BUILDING = 2
THINKING = 3
EDITING_FILE = 4
SEARCHING_LOCAL = 5
RUNNING_COMMAND = 6
PROVING = 7
READING_FILES = 8
REVIEWING = 9
FINISHED = 10
ERROR = 11
READING_LEAN = 12
SEARCHING_EXTERNAL = 13
RUNNING_LEAN = 14
QUESTION = 15
SUMMARY = 16
AGENT_QUESTION = 17
AGENT_FOLLOW_UP = 18
QUESTION records a user question about a task. SUMMARY records a task summary. AGENT_QUESTION records a clarifying question from Aristotle. AGENT_FOLLOW_UP records a message from Aristotle after the summary, such as how to pick up a task that ran out of time.
EventStatus
class EventStatus(IntEnum):
UNKNOWN = 0
COMPLETE = 1
SENT = 2
SENT marks an event still on its way: a MESSAGE that project.ask() sent to a running task, until the task picks it up, or an AGENT_QUESTION event from before Question, until it's answered. Every other event is COMPLETE, a question's event included: the question's own state is on the Question.
Errors
Network and API failures raise AristotleAPIError. When the failure originates from an HTTP response, the exception's status_code attribute is set. Its subclasses say more:
AristotleNotFoundError: no project or task you can see has the ID.AristotleProjectLimitError: too many of your runs are in progress or queued.AristotleNotProcessedError: the request failed before Aristotle acted on it, so it is safe to send again.AristotleOutcomeUnknownError: the request failed after Aristotle may have acted on it, so check before sending it again.ProjectHasNoFilesError:project.get_files()found nothing to download.
from aristotlelib import AristotleAPIError
try:
project = await Project.create(prompt="My prompt")
except AristotleAPIError as e:
print(f"API error (status {e.status_code}): {e}")