# How the AI Agent System Integrates Team Memory and Tools in the Macro Codebase

> Discover how the Macro AI agent system integrates team memory and tools for context-aware automation. Explore dynamic tool registration and PostgreSQL-persisted memory.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: internals
- Published: 2026-08-15

---

**The Macro AI agent system combines dynamic tool registration, PostgreSQL-persisted team memory, and structured system prompt assembly to enable context-aware, tool-enabled automation workflows.**

The `macro-inc/macro` repository implements a Rust-based **AI agent** that retrieves personalized team memory and invokes tools through a unified prompting flow. This article examines the three core components—**Agent Loop**, **Memory Service**, and **PG Memory Repository**—and how they coordinate to deliver contextual responses.

## Agent Loop: Session Creation and Dynamic Tool Registration

The **`AgentLoop`** in [`crates/agent/src/agent_loop.rs`](https://github.com/macro-inc/macro/blob/main/crates/agent/src/agent_loop.rs) orchestrates per-request execution. When a request arrives, `AgentLoop::session` (or `test_session` for tests) constructs a `Session` that encapsulates the LLM, tools, and streaming infrastructure.

```rust
let session = AgentLoop::new(recorder)
    .session(toolset, Arc::new(tool_context), &system_prompt, usage_ctx)
    .await;

```

During session initialization (lines [73-120](https://github.com/macro-inc/macro/blob/main/crates/agent/src/agent_loop.rs#L73-L120)), the loop performs three critical operations:

- **Builds a searchable catalog** via `toolset.searchable_catalog()` containing static and MCP (Macro-Customer-Portal) tools
- **Creates dynamic tool adapters** using `DynToolSetAdapter::from_toolset` and registers each with a live `ToolServer` through `handle.add_dynamic_tool(adapter).await`
- **Bundles request context** with the user ID and cancellation token via `RequestContext::new(usage_ctx.user.clone())`

Dynamic tool loading continues throughout the session lifecycle. The `register_loaded` closure ([lines 128-176](https://github.com/macro-inc/macro/blob/main/crates/agent/src/agent_loop.rs#L128-L176)) receives discovered `SearchableTool`s and hot-loads them into the running `ToolServer`.

## Memory Service: Fetching and Generating Team Memory

The **`MemoryServiceImpl`** in [`crates/memory/src/domain/service.rs`](https://github.com/macro-inc/macro/blob/main/crates/memory/src/domain/service.rs) provides the `<user_memory>` block injected into system prompts. Every agent task calls `get_or_generate_memory` before execution:

```rust
match memory_service.get_or_generate_memory(owner.clone()).await {
    Ok(memory) => memory,
    Err(e) => {
        tracing::warn!(error=?e, %owner, "failed to fetch user memory; running without it");
        None
    }
}

```

This invocation appears in [`agent_task.rs`](https://github.com/macro-inc/macro/blob/main/agent_task.rs) [lines 59-81](https://github.com/macro-inc/macro/blob/main/services/scheduled_action/src/outbound/inprocess_executor/agent_task.rs#L59-L81). The service checks `PgMemoryRepo::get_latest_memory` for freshness against the `MAX_AGE` constant (24 hours). Stale or missing memory triggers an asynchronous generation pipeline:

1. **Research phase**: Calls the LLM with `GENERATE_MEMORY_PROMPT` to synthesize context from documents, projects, and emails
2. **Validation phase**: Sends the draft to a judge LLM using `JUDGE_PROMPT` for quality scoring
3. **Persistence phase**: Stores accepted memory via `PgMemoryRepo::save_memory`

The core orchestration lives in [lines 10-25](https://github.com/macro-inc/macro/blob/main/crates/memory/src/domain/service.rs#L10-L25); the generation flow occupies [lines 61-78](https://github.com/macro-inc/macro/blob/main/crates/memory/src/domain/service.rs#L61-L78).

## PG Memory Repository: Persistence Layer

The **`PgMemoryRepo`** in [`crates/memory/src/outbound/pg_memory_repo.rs`](https://github.com/macro-inc/macro/blob/main/crates/memory/src/outbound/pg_memory_repo.rs) ([lines 19-55](https://github.com/macro-inc/macro/blob/main/crates/memory/src/outbound/pg_memory_repo.rs#L19-L55)) provides SQLx-backed operations against the `memory` table:

- `get_latest_memory(user)` — Retrieves the most recent memory record with timestamp
- `save_memory(memory, user)` — Persists validated memory content
- Record age validation supports the `MAX_AGE` freshness check

This repository enables the memory service to treat team context as durable, queryable state rather than ephemeral prompt injection.

## System Prompt Assembly: Combining Memory and Tools

The agent constructs its system prompt through explicit string concatenation in [`agent_task.rs`](https://github.com/macro-inc/macro/blob/main/agent_task.rs) [lines 24-30](https://github.com/macro-inc/macro/blob/main/services/scheduled_action/src/outbound/inprocess_executor/agent_task.rs#L24-L30):

```rust
let system_prompt = match user_memory {
    Some(memory) => format!("{}\\n{}\\n<user_memory>\\n{}\\n</user_memory>\\n{}",
                            tools.prompt, SCHEDULED_AGENT_PROMPT, memory, agent_task.prompt),
    None => format!("{}\\n{}", tools.prompt, agent_task.prompt),
};

```

The four components are:

| Component | Source | Purpose |
|-----------|--------|---------|
| `tools.prompt` | `ai_tools::all_tools()` | Describes all available tools to the LLM |
| `SCHEDULED_AGENT_PROMPT` | Constant | Signals automation-triggered execution context |
| `<user_memory>` | `MemoryServiceImpl` | Injects personalized team context (optional) |
| `agent_task.prompt` | User input | High-level instruction for the specific task |

This assembled prompt is passed to `AgentLoop::session` and transmitted as part of the LLM request payload.

## Streaming Execution and Tool Invocation

Inside the session, user messages convert to Rig-compatible format and stream to the model:

```rust
let rig_messages = agent::to_rig_messages(&[user_msg]);
let mut stream = session.send_message(rig_messages).await?;

```

Lines [47-52](https://github.com/macro-inc/macro/blob/main/services/scheduled_action/src/outbound/inprocess_executor/agent_task.rs#L47-L52) in [`agent_task.rs`](https://github.com/macro-inc/macro/blob/main/agent_task.rs) demonstrate this pattern. The stream consumes with `idle_timeout`, accumulating parts into a `StreamAccumulator`. Final assistant text filters from these parts and optionally routes to notification services.

When the LLM emits tool calls, the `ToolServer` intercepts and executes them. The server maintains a **live registry** blending:

- **Pre-registered static tools** loaded at startup
- **Dynamically loaded tools** from the `register_loaded` closure

## Complete Integration Flow

The Macro AI agent system follows this coordinated sequence:

1. `AgentLoop::session` creates `Session` and `ToolServer` with dynamic tool adapters
2. `MemoryServiceImpl` supplies fresh `<user_memory>` via `PgMemoryRepo` or generates new context
3. System prompt assembles `tools.prompt` + `SCHEDULED_AGENT_PROMPT` + `user_memory` + `agent_task.prompt`
4. Session streams LLM response; tool calls route through live `ToolServer` registry
5. Assistant output persists to chat database with optional notification dispatch

## Summary

- **Agent Loop** ([`crates/agent/src/agent_loop.rs`](https://github.com/macro-inc/macro/blob/main/crates/agent/src/agent_loop.rs)) manages per-request sessions with dynamic tool loading and streaming LLM completion
- **Memory Service** ([`crates/memory/src/domain/service.rs`](https://github.com/macro-inc/macro/blob/main/crates/memory/src/domain/service.rs)) fetches or generates 24-hour-fresh team memory through research and judge LLM validation
- **PG Memory Repository** ([`crates/memory/src/outbound/pg_memory_repo.rs`](https://github.com/macro-inc/macro/blob/main/crates/memory/src/outbound/pg_memory_repo.rs)) provides durable storage for memory documents
- **System Prompt Assembly** concatenates tool descriptions, automation context, optional user memory, and task instructions into the LLM prompt
- **Tool Execution** routes through a live `ToolServer` registry supporting both static and dynamically discovered tools

## Frequently Asked Questions

### How does the Macro agent decide when to generate new team memory?

The `MemoryServiceImpl` compares the latest stored memory timestamp against the `MAX_AGE` constant (24 hours). Records older than this threshold or completely missing entries trigger the asynchronous generation pipeline involving research and judge LLM calls.

### What happens if team memory fetching fails?

The agentTask gracefully degrades. As shown in [`agent_task.rs`](https://github.com/macro-inc/macro/blob/main/agent_task.rs) lines 59-81, fetch failures log a warning with `tracing::warn!` and proceed with `None` memory—the system prompt omits the `<user_memory>` block rather than failing the entire request.

### Can tools be added after session initialization?

Yes. The `register_loaded` closure in `AgentLoop::session_with` ([lines 128-176](https://github.com/macro-inc/macro/blob/main/crates/agent/src/agent_loop.rs#L128-L176)) enables dynamic tool discovery and hot-loading throughout the session lifecycle, extending the `ToolServer` registry beyond startup-time static tools.

### Where does the tool description prompt originate?

The `tools.prompt` component comes from `ai_tools::all_tools()` in [`crates/ai_tools/src/subagent.rs`](https://github.com/macro-inc/macro/blob/main/crates/ai_tools/src/subagent.rs), providing a static catalog of available tools that is injected into every system prompt construction.