Skip to main content
Loopy has five extension points, from zero-code Markdown files to full Java SPIs.

Skills

Domain knowledge the agent reads on demand

Subagents

Specialist agents for delegation

Tool Profiles

New tools via Java SPI

Skills

Skills are the fastest way to make Loopy smarter. A skill is a Markdown file with YAML frontmatter. The agent sees skill names and descriptions upfront but only loads full content when relevant — no tokens wasted.

Create a skill

Place a SKILL.md in your project:
.claude/skills/my-conventions/SKILL.md
---
name: my-conventions
description: Team coding conventions for our Spring Boot services
---

# Instructions

When working on this codebase:
- Use constructor injection, never field injection
- All REST endpoints return ProblemDetail for errors (RFC 9457)
- Tests use @WebMvcTest with MockMvc, not @SpringBootTest
- Entity IDs are UUIDs, never auto-increment
Next time you run Loopy in that directory, the agent discovers the skill automatically.

Where skills live

LocationPathUse case
Project.claude/skills/*/SKILL.mdTeam conventions, checked into the repo
Global~/.claude/skills/*/SKILL.mdPersonal skills across all projects
ClasspathMETA-INF/skills/*/SKILL.md in JARsPublished skill packages (Maven dep)

Install from the catalog

Loopy ships with 23+ curated skills from 8 publishers:
/skills search testing
/skills info systematic-debugging
/skills add systematic-debugging

Publish skills as a JAR (SkillsJars)

Package skills as a Maven dependency so teams get them automatically:
my-skills.jar
└── META-INF/skills/
    └── my-org/my-repo/api-design/
        └── SKILL.md
Add the JAR to pom.xml and Loopy discovers it on the classpath. Skills follow the agentskills.io spec — they work in 40+ agentic CLIs, not just Loopy.

Subagents

Subagents are specialist agents the main agent delegates to via the Task tool. Define them as Markdown files — no Java required.

Create a subagent

Create .claude/agents/test-runner.md:
---
name: test-runner
description: Runs tests and reports pass/fail summary
tools: Bash, Read
---

You are a testing specialist. When invoked:

1. Run `./mvnw test` in the working directory
2. Parse output for pass/fail/skip counts
3. If tests fail, read the relevant test source files
4. Report a concise summary: what passed, what failed, and why
The main agent delegates testing tasks to this subagent automatically based on the description field.

Frontmatter fields

FieldRequiredDescription
nameYesUnique identifier (lowercase, hyphens)
descriptionYesWhen to use — the main agent reads this to decide
toolsNoAllowed tools (comma-separated). Inherits all if omitted
modelNohaiku, sonnet, or opus

Tips

  • Keep description specific — “Runs tests and reports results” routes better than “helps with testing”
  • Restrict tools to what the subagent needs. A test runner doesn’t need Edit
  • Subagents run in isolated context windows — they can’t see the main conversation
  • Subagents cannot spawn other subagents (the Task tool is excluded automatically)

Tool Profiles (Java SPI)

Add new tools the agent can call. Implement a Java interface, package as a JAR, and Loopy discovers it at startup via ServiceLoader.

Implement ToolProfileContributor

public class DatabaseToolProfile implements ToolProfileContributor {

    @Override
    public String profileName() {
        return "database-tools";
    }

    @Override
    public List<ToolCallback> tools(ToolFactoryContext ctx) {
        return List.of(
            ToolCallbacks.from(new QueryTool(ctx.workingDirectory())),
            ToolCallbacks.from(new SchemaTool(ctx.workingDirectory()))
        );
    }
}

Register via ServiceLoader

Create META-INF/services/io.github.markpollack.loopy.tools.ToolProfileContributor:
com.example.tools.DatabaseToolProfile

What ToolFactoryContext provides

FieldTypeDescription
workingDirectoryPathAgent’s working directory
chatModelChatModelThe active LLM (for tools that need AI)
commandTimeoutDurationTool execution timeout (default 120s)
interactivebooleanTrue in TUI mode, false in print/REPL

Built-in profiles

ProfileDescription
devFull interactive toolset (bash, file I/O, search, skills, subagents)
bootSpring Boot scaffolding tools
headlessSame as dev minus AskUserQuestion (for CI/CD)
readonlyRead-only: file read, grep, glob, list directory
Custom profiles load alongside built-in profiles, not replacing them.

Listeners

Observe what the agent does without changing its behavior.

ToolCallListener

Fires around every tool execution:
public class CostTracker implements ToolCallListener {

    @Override
    public void onToolExecutionCompleted(String runId, int turn,
            AssistantMessage.ToolCall toolCall, String result,
            Duration duration) {
        log.info("Tool {} took {}ms", toolCall.name(), duration.toMillis());
    }
}

AgentLoopListener

Fires at loop lifecycle boundaries:
public class ProgressReporter implements AgentLoopListener {

    @Override
    public void onLoopCompleted(String runId, LoopState state,
            TerminationReason reason) {
        System.err.printf("Done: %s (%d turns)%n", reason, state.turns());
    }
}
Wire listeners through the MiniAgent builder:
var agent = MiniAgent.builder()
    .config(config)
    .model(chatModel)
    .toolCallListener(new CostTracker())
    .loopListener(new ProgressReporter())
    .build();
Listener methods should not throw exceptions — exceptions are logged and swallowed to avoid crashing the agent loop.

Programmatic API

Embed Loopy’s agent in other Java applications:
LoopyAgent agent = LoopyAgent.builder()
    .workingDirectory(Path.of("/path/to/project"))
    .build();

LoopyResult result = agent.run("add input validation to UserController");

Multi-step with session memory

Context is preserved across run() calls by default:
LoopyAgent agent = LoopyAgent.builder()
    .workingDirectory(workspace)
    .maxTurns(80)
    .build();

agent.run("plan a refactoring of the service layer");
agent.run("now execute the plan");  // sees the previous conversation

Builder options

MethodDescriptionDefault
.model(String)Model IDclaude-sonnet-4-6
.workingDirectory(Path)Agent’s working directoryrequired
.systemPrompt(String)Custom system promptBuilt-in coding prompt
.maxTurns(int)Max loop iterations80
.costLimit(double)Max cost in dollars$5.00
.sessionMemory(boolean)Preserve context across run() callstrue
.timeout(Duration)Overall loop timeout10 min
.disabledTools(Set<String>)Tools to excludenone

Custom endpoints (vLLM, LM Studio)

LoopyAgent agent = LoopyAgent.builder()
    .baseUrl("http://localhost:1234/v1")
    .apiKey("lm-studio")
    .model("local-model")
    .workingDirectory(workspace)
    .build();