Builder
Core reference

Builder: teardown

The teardown module provides the Teardown class, a dedicated cleanup orchestrator for the jazzmine framework. Modern AI agents hold numerous heavyweight resources—HTTP connection pools to LLM providers, active Docker containers, persistent database connections, and background asyncio tasks. The Teardown object ensures that all these resources are cleanly and systematically released when the application shuts down.

1. Behavior and Context

In the jazzmine architecture, Teardown implements a Last-In-First-Out (LIFO) execution model.

  • Registration Phase: As the AgentBuilder successfully initializes each component (Database → LLMs → Docker Pool → HTTP Server → Agent), it registers a corresponding cleanup callback (a Python coroutine) with the Teardown object.
  • Execution Phase: When the application exits, calling the Teardown object executes these callbacks in reverse order.
  • Why LIFO? LIFO guarantees that dependent resources are not destroyed before the components relying on them. For example, it ensures the Agent finishes its background summarization tasks before the Database connections are closed, and it stops Docker containers before the Docker daemon client is released.

2. Purpose

  • Graceful Shutdown: Preventing data corruption by allowing in-flight database writes and background tasks to complete natively.
  • Resource Leak Prevention: Ensuring no orphaned Docker containers (which consume RAM and CPU) are left running on the host machine after the Python process dies.
  • Connection Management: Properly closing httpx and database connection pools to prevent socket exhaustion and asyncio warnings.
  • Error Isolation: Ensuring that if one cleanup task fails, the remaining tasks still execute.

3. High-Level API (Usage)

For most developers, the Teardown object is received as the second element of the tuple returned by AgentBuilder.build(). It acts as an asynchronous callable.

Example: Standard Application Lifecycle

python
import asyncio
import signal
from jazzmine.core.agent.builder import AgentBuilder

async def main():
    # 1. Build the agent and receive the teardown orchestrator
    agent, teardown = await AgentBuilder(name="Aria", agent_id="v1", personality="...").build()

    try:
        # 2. Run your application (e.g., listen for messages, run the HTTP server)
        print("Agent is running. Press Ctrl+C to exit.")
        
        # Keep the event loop alive
        stop_event = asyncio.Event()
        loop = asyncio.get_running_loop()
        for sig in (signal.SIGINT, signal.SIGTERM):
            loop.add_signal_handler(sig, stop_event.set)
            
        await stop_event.wait()
        
    finally:
        # 3. Execute the graceful shutdown sequence
        print("\nInitiating graceful shutdown...")
        await teardown()
        print("Shutdown complete.")

if __name__ == "__main__":
    asyncio.run(main())

You can also hook your own application's cleanup logic into the framework's teardown sequence.

python
# Custom cleanup function
async def close_my_custom_api():
    print("Closing custom API connections...")
    await my_api_client.aclose()

# Register it so it runs alongside the Jazzmine cleanups
teardown.register(close_my_custom_api)

4. Detailed Functionality

Class: Teardown

register(fn: Callable[[], Coroutine])

Functionality: Adds an asynchronous callback to the internal list.

How it works: The AgentBuilder uses this internally. As it successfully provisions a component, it wraps the component's close(), shutdown(), or drain() method in an async closure and pushes it onto the _callbacks list.


run()

Functionality: Iterates through the registered callbacks in reverse order (LIFO) and awaits each one.

Execution Order Guarantee (from Builder): When triggered by the AgentBuilder, the execution order is strictly guaranteed:

  1. agent.drain(): Waits for background summarization and memory tasks to finish.
  2. server.stop(): Stops the FastAPI/Uvicorn server (if configured) so no new requests arrive.
  3. pool.shutdown(): Kills and removes all running Docker sandboxes.
  4. llm(s).aclose(): Closes HTTP connection pools to LLM providers.
  5. store.close(): Releases Postgres/MongoDB/JSON connection pools.
  6. Temp File Cleanup: Deletes any auto-generated temporary JSON files.

__call__()

Functionality: A syntactic sugar alias for run(). Allows you to execute the teardown simply by calling await teardown().


5. Error Handling

  • Resilient Execution: The run() method wraps every single callback in a try...except Exception block.
  • Soft Failures: If a specific cleanup routine fails (e.g., the Docker daemon crashes while trying to stop a container, throwing a docker.errors.APIError), the Teardown object catches the exception, logs a warning ("Teardown callback raised: ..."), and immediately proceeds to the next callback. This ensures that one misbehaving component does not prevent the database connections or other vital resources from being closed properly.

6. Remarks

  • Idempotency Assumption: The functions registered with Teardown should be designed to be idempotent (safe to call multiple times). While Teardown only calls them once, robust teardown logic assumes the component might have already partially closed.
  • Event Loop Safety: Teardown relies on asyncio. It must be executed within an active event loop. Do not use asyncio.run(teardown()) if the components were tied to a different event loop.
  • Testing: In unit testing environments, utilizing the Teardown object is critical to prevent cascading test failures caused by connection pool exhaustion across multiple test suites.