AI Agent Learning
L13

Full Reference Agent

Collaboration

Capstone — Everything Combined

829 LOC0 toolsAll mechanisms combined
The capstone combines every mechanism from Lessons 1–12 into one agent
L00 > L01 > L02 > L03 > L04 > L05 > L06 > L07 > L08 > L09 > L10 > L11 > L12 > [ L13 ]

"Every mechanism, one agent."


What This Is

This is not a teaching lesson. It is the capstone reference implementation that combines every mechanism from Lessons 1-12 into a single, production-ready agent. Use it as:

  • A reference for how all components integrate
  • A starting point for building real agents
  • A test harness for verifying all subsystems work together

Architecture Overview

+====================================================================+
|                        Lesson13RunSimple                            |
|                                                                     |
|  +-------------------+    +-------------------+    +--------------+ |
|  |   Agent Loop      |    |   Compression     |    |  Skill       | |
|  |   (L01/L02)       |    |   Pipeline (L06)  |    |  Loader (L05)| |
|  |                    |    |                   |    |              | |
|  |  while(true):      |    |  micro-compact    |    |  SKILL.md   | |
|  |   drain bg notifs  |    |  auto-compact     |    |  files in   | |
|  |   check inbox      |    |  manual compress  |    |  skills/    | |
|  |   LLM call         |    |  transcript save  |    +--------------+ |
|  |   tool dispatch    |    +-------------------+                    |
|  +-------------------+                                              |
|                                                                     |
|  +-------------------+    +-------------------+    +--------------+ |
|  |   TodoManager     |    |   TaskManager     |    |  Background  | |
|  |   (L03)           |    |   (L07)           |    |  Manager(L08)| |
|  |                    |    |                   |    |              | |
|  |  in-memory list    |    |  .tasks/*.json    |    |  daemon      | |
|  |  nag reminder      |    |  blockedBy/blocks |    |  threads     | |
|  |  max 1 in_progress |    |  claim by owner   |    |  notif queue | |
|  +-------------------+    +-------------------+    +--------------+ |
|                                                                     |
|  +-------------------+    +-------------------+    +--------------+ |
|  |   Subagent        |    |   MessageBus      |    |  Teammate    | |
|  |   (L04)           |    |   (L09)           |    |  Manager     | |
|  |                    |    |                   |    |  (L09-L11)   | |
|  |  spawn -> run ->   |    |  .team/inbox/     |    |              | |
|  |  return summary    |    |  *.jsonl files    |    |  config.json | |
|  |  own tool set      |    |  append + drain   |    |  autonomous  | |
|  +-------------------+    +-------------------+    |  idle/poll   | |
|                                                    |  protocols   | |
|                                                    +--------------+ |
+====================================================================+

Component Integration

Initialization: All Managers Created Together

@Override
public void run(String userPrompt) {
    workDir = Paths.get(System.getProperty("user.dir"));
    teamDir = workDir.resolve(".team");
    inboxDir = teamDir.resolve("inbox");
    tasksDir = workDir.resolve(".tasks");
    skillsDir = workDir.resolve("skills");
    transcriptDir = workDir.resolve(".transcripts");

    client = OpenAIOkHttpClient.builder()
        .apiKey(apiKey).baseUrl(baseUrl)
        .proxy(new Proxy(Proxy.Type.HTTP,
            new InetSocketAddress(proxyHost, proxyPort)))
        .build();

    // Initialize ALL managers
    todo = new TodoManager();           // L03
    skills = new SkillLoader(skillsDir); // L05
    taskMgr = new TaskManager(tasksDir); // L07
    bg = new BackgroundManager(workDir); // L08
    bus = new MessageBus(inboxDir);      // L09
    team = new TeammateManager();        // L09-L11

    String sysPrompt = buildSystemPrompt();
    agentLoop(messages, sysPrompt);
}

The Agent Loop: Everything Converges

The main loop integrates all subsystems in a specific order:

private void agentLoop(List<ChatCompletionMessageParam> messages,
                       String sysPrompt) {
    int roundsWithoutTodo = 0;

    while (true) {
        // 1. L06: Compression pipeline
        microCompact(messages);
        if (estimateTokens(messages) > TOKEN_THRESHOLD) {
            List<ChatCompletionMessageParam> compressed =
                autoCompact(messages);
            messages.clear();
            messages.addAll(compressed);
        }

        // 2. L08: Drain background notifications
        List<Map<String, Object>> notifs = bg.drain();
        if (!notifs.isEmpty()) {
            messages.add(ofUser("<background-results>\n" + /* ... */));
            messages.add(ofAssistant("Noted background results."));
        }

        // 3. L09: Check lead inbox
        List<Map<String, Object>> inbox = bus.readInbox("lead");
        if (!inbox.isEmpty()) {
            messages.add(ofUser("<inbox>" + listToJson(inbox) + "</inbox>"));
            messages.add(ofAssistant("Noted inbox messages."));
        }

        // 4. LLM call with ALL tools registered
        ChatCompletion completion = client.chat().completions()
            .create(ChatCompletionCreateParams.builder()
                .model(ChatModel.of(modelName))
                .messages(messages)
                .tools(createTools())  // all tools from all lessons
                .addSystemMessage(sysPrompt)
                .build());

        // 5. Tool dispatch
        for (ChatCompletionMessageToolCall tc : toolCalls) {
            String output = executeTool(toolName, args);
            // ...
        }

        // 6. L03: Nag reminder for TodoWrite
        roundsWithoutTodo = usedTodo ? 0 : roundsWithoutTodo + 1;
        if (todo.hasOpenItems() && roundsWithoutTodo >= 3) {
            log.info("[nag reminder injected]");
        }

        // 7. L06: Manual compression if requested
        if (manualCompress) {
            messages.clear();
            messages.addAll(autoCompact(messages));
        }
    }
}

Inner Class: TodoManager (L03)

In-memory checklist with nag reminders:

static class TodoManager {
    private List<Map<String, Object>> items = new ArrayList<>();

    public String update(List<Map<String, Object>> newItems) {
        if (newItems.size() > 20)
            throw new IllegalArgumentException("Max 20 todos");

        int ip = 0;
        for (Map<String, Object> item : newItems) {
            if ("in_progress".equals(item.get("status"))) ip++;
        }
        if (ip > 1)
            throw new IllegalArgumentException(
                "Only one in_progress allowed");

        items = new ArrayList<>(newItems);
        return render();
    }

    public boolean hasOpenItems() {
        return items.stream()
            .anyMatch(t -> !"completed".equals(t.get("status")));
    }
}

Inner Class: SkillLoader (L05)

Loads SKILL.md files from the skills/ directory and injects them into the system prompt:

static class SkillLoader {
    private final Map<String, Map<String, String>> skills = new HashMap<>();

    public SkillLoader(Path skillsDir) {
        Files.walk(skillsDir)
            .filter(p -> p.getFileName().toString().equals("SKILL.md"))
            .forEach(p -> {
                String text = Files.readString(p);
                String name = p.getParent().getFileName().toString();
                skills.put(name, Map.of(
                    "body", text,
                    "description", "Skill: " + name));
            });
    }

    public String load(String name) {
        Map<String, String> s = skills.get(name);
        if (s == null) return "Error: Unknown skill '" + name + "'";
        return "<skill name=\"" + name + "\">\n"
            + s.get("body") + "\n</skill>";
    }

    public String getDescriptions() {
        return skills.entrySet().stream()
            .map(e -> "  - " + e.getKey() + ": "
                + e.getValue().get("description"))
            .collect(Collectors.joining("\n"));
    }
}

Inner Class: TaskManager (L07)

File-based task graph with dependency resolution and owner-based claiming:

static class TaskManager {
    public String create(String subject, String description) { /* ... */ }
    public String get(int taskId) { /* ... */ }
    public String update(int taskId, String status,
        List<Integer> addBlockedBy, List<Integer> addBlocks) { /* ... */ }
    public String listAll() { /* ... */ }
    public String claim(int taskId, String owner) { /* ... */ }
}

Inner Class: BackgroundManager (L08)

Daemon threads with notification queue:

static class BackgroundManager {
    private final Map<String, Map<String, Object>> tasks =
        new ConcurrentHashMap<>();
    private final List<Map<String, Object>> notifications =
        new CopyOnWriteArrayList<>();

    public String run(String command, int timeout) { /* ... */ }
    public String check(String taskId) { /* ... */ }
    public List<Map<String, Object>> drain() { /* ... */ }
}

Inner Class: MessageBus (L09)

Extends the Lesson 10 MessageBus for JSONL inboxes:

static class MessageBus extends Lesson10RunSimple.MessageBus {
    public MessageBus(Path inboxDir) { super(inboxDir); }
}

Inner Class: TeammateManager (L09-L11)

Full autonomous teammates with protocols:

class TeammateManager {
    public String spawn(String name, String role, String prompt) {
        // Creates thread with autonomousLoop
    }

    private void autonomousLoop(String name, String role, String prompt) {
        while (true) {
            // WORK PHASE: agent loop with LLM calls
            // IDLE PHASE: poll inbox + task board
            // Shutdown on timeout or shutdown_request
        }
    }
}

Subagent Delegation (L04)

The task tool spawns a sub-agent with its own conversation and limited tools:

private String runSubagent(String prompt, String agentType) {
    List<ChatCompletionTool> subTools = createSubagentTools();
    List<ChatCompletionMessageParam> subMessages = new ArrayList<>();
    subMessages.add(ofUser(prompt));

    for (int i = 0; i < 30; i++) {
        ChatCompletion completion = client.chat().completions()
            .create(ChatCompletionCreateParams.builder()
                .model(ChatModel.of(modelName))
                .messages(subMessages)
                .tools(subTools)
                .build());

        // Process tool calls or break on stop
        if (finishReason != TOOL_CALLS) break;
    }

    return lastResponse.content().orElse("(subagent failed)");
}

Compression Pipeline (L06)

Three compression strategies working together:

// 1. Micro-compact: truncate old tool results to save context
private void microCompact(List<ChatCompletionMessageParam> messages) {
    // Tool results beyond the last 6 messages get truncated to 200 chars
}

// 2. Auto-compact: triggered when tokens exceed threshold
private List<ChatCompletionMessageParam> autoCompact(
        List<ChatCompletionMessageParam> messages) {
    // Save transcript to .transcripts/
    // Ask LLM to summarize conversation
    // Replace messages with summary
}

// 3. Manual: agent calls "compress" tool explicitly
if ("compress".equals(toolName)) manualCompress = true;

System Prompt: Skills Integration

The system prompt dynamically includes skill descriptions:

private String buildSystemPrompt() {
    return systemPrompt + "\n"
        + "You are a coding agent at " + workDir + ".\n"
        + "Use tools to solve tasks.\n"
        + "Prefer task_create/task_update/task_list for multi-step work.\n"
        + "Use TodoWrite for short checklists.\n"
        + "Use task for subagent delegation.\n"
        + "Use load_skill for specialized knowledge.\n"
        + "Skills:\n" + skills.getDescriptions();
}

Full Tool Registry

All tools from all lessons, registered in one createTools() method:

SourceToolDescription
L02bashRun a shell command
L02read_fileRead file contents
L02write_fileWrite content to file
L02edit_fileReplace exact text in file
L03TodoWriteUpdate the todo checklist
L04taskDelegate to a subagent
L05load_skillLoad a skill by name
L06compressManually trigger context compression
L07task_createCreate a task on the board
L07task_updateUpdate task status/dependencies
L07task_listList all tasks
L07task_getGet task details by ID
L08background_runRun command in background thread
L08check_backgroundCheck background task status
L09spawn_teammateSpawn an autonomous teammate
L09list_teammatesList team members
L09send_messageSend message to a teammate
L09read_inboxRead and drain the lead inbox
L09broadcastBroadcast to all teammates
L10shutdown_requestRequest teammate shutdown
L10plan_approvalApprove/reject a teammate's plan

Directory Structure at Runtime

  project/
    |
    +-- .tasks/                    <-- L07: Task JSON files
    |     +-- task_1.json
    |     +-- task_2.json
    |
    +-- .team/                     <-- L09: Team config + inboxes
    |     +-- config.json
    |     +-- inbox/
    |           +-- lead.jsonl
    |           +-- alice.jsonl
    |
    +-- .transcripts/              <-- L06: Compressed transcripts
    |     +-- transcript_17112345.jsonl
    |
    +-- skills/                    <-- L05: Skill definitions
          +-- git/
          |     +-- SKILL.md
          +-- docker/
                +-- SKILL.md

Data Flow

  User prompt
       |
       v
  +-- Agent Loop ------------------------------------------------+
  |                                                               |
  |  1. microCompact(messages)          [L06]                     |
  |  2. autoCompact if over threshold   [L06]                     |
  |  3. bg.drain() -> inject notifs     [L08]                     |
  |  4. bus.readInbox("lead") -> inject [L09]                     |
  |  5. LLM call with all tools                                  |
  |  6. Tool dispatch:                                            |
  |     +-- bash/read/write/edit        [L02]                     |
  |     +-- TodoWrite + nag check       [L03]                     |
  |     +-- task (subagent)             [L04]                     |
  |     +-- load_skill                  [L05]                     |
  |     +-- compress                    [L06]                     |
  |     +-- task_create/update/list/get [L07]                     |
  |     +-- background_run/check        [L08]                     |
  |     +-- spawn/send/broadcast/inbox  [L09]                     |
  |     +-- shutdown_request/plan       [L10]                     |
  |  7. Nag reminder if no TodoWrite    [L03]                     |
  |  8. Manual compress if requested    [L06]                     |
  |                                                               |
  +---------------------------------------------------------------+
       |
       v
  Assistant response

What Changed (from Lesson 12)

AspectLesson 12 (Worktree Isolation)Lesson 13 (Full Agent)
ScopeSingle mechanism (worktrees)All mechanisms combined
ManagersWorktreeManager, EventBusTodoManager, SkillLoader, TaskManager, BackgroundManager, MessageBus, TeammateManager
Tools16 tools21+ tools from all lessons
CompressionNot includedFull pipeline (micro + auto + manual)
SubagentsNot includedSubagent delegation via task tool
SkillsNot includedSkillLoader with SKILL.md files
TodoWriteNot includedIn-memory checklist with nag reminders
System promptStaticDynamic with skill descriptions

Try It

  1. Run Lesson13RunSimple with a complex prompt: "Plan a REST API project in the trysamples directory. Create tasks for each component. Spawn alice and bob to work on different parts. Use background tasks for builds."
  2. Observe all subsystems working together:
    • Tasks appear in .tasks/
    • Teammates communicate via .team/inbox/
    • Background builds run in daemon threads
    • Context compression triggers when the conversation grows
  3. Check .transcripts/ for saved conversation history after compression.
  4. Add a skills/ directory with SKILL.md files and use load_skill to inject domain knowledge.

Source: Lesson13RunSimple.java