Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@avcodes/mi

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@avcodes/mi

agentic coding in 30 loc. a loop, four tools, and an llm.

Source
npmnpm
Version
1.0.9
Version published
Weekly downloads
553
-72.68%
Maintainers
1
Weekly downloads
 
Created
Source

Splash image

https://github.com/user-attachments/assets/9289d105-5a40-442d-b1b5-773723c95c13

agentic coding in 30 loc. a loop, four tools, and an llm.

features

  • bash (optional timeout=<ms> kills after delay, bg=truthy detaches and returns pid+log), read, write, and skill tools
  • skill tool loads SKILL.md playbooks from ~/.agents/skills/ (descriptions auto-advertised in system prompt so the model matches tasks to skills)
  • accepts stdin via pipes (e.g. echo "do this" | mi)
  • file context ingestion via -f <file> argument
  • automatic ingestion of AGENTS.md if it exists in current directory
  • chat REPL by default with interactive /reset command to clear history
  • graceful SIGINT (Ctrl+C) handling for bash child processes
  • non-interactive mode with -p 'prompt' arg

install

# run directly
npx @avcodes/mi

# or install globally
npm i -g @avcodes/mi
mi

usage

# interactive repl (type /reset to clear history)
OPENAI_API_KEY=sk-... mi

# one-shot (run once, exit)
mi -p 'refactor auth.js to use bcrypt'

# load additional context from a file
mi -f error.log -p 'why is this crashing?'

# pipe stdin to the agent
echo "write a python script that prints hello world" | mi

# local models via any openai-compatible api
MODEL=qwen3.5:4b OPENAI_BASE_URL=http://localhost:33821 mi

env

vardefaultwhat
OPENAI_API_KEYapi key
OPENAI_BASE_URLhttps://api.openai.comapi base url (ollama, lmstudio, litellm, etc)
MODELgpt-5.4model name
SYSTEM_PROMPTbuilt-in agent promptoverride the system prompt entirely

deep dive

an agentic harness is surprisingly simple. it's a loop that calls an llm, checks if it wants to use tools, executes them, feeds results back, and repeats. here's how each part works.

tools

the agent needs to affect the outside world. tools are just functions that take structured args and return a string. four tools is enough for a general-purpose coding agent:

const tools = {
  bash: ({ command, timeout, bg }) => execShell(command, timeout, bg), // run any shell command
  read:  ({ path }) => readFileSync(path, 'utf8'),  // read a file
  write: ({ path, content }) => (writeFileSync(path, content), 'ok'), // write a file
  skill: ({ name }) => loadSkillMarkdown(name), // load agent skill from ~/.agents/skills/
};

bash gives the agent access to the entire system: git, curl, compilers, package managers. optional timeout=<ms> kills the process after the given delay and resolves with [timeout]. optional bg=truthy runs the command detached and returns pid:X log:/tmp/mi-*.log immediately. read and write handle files. skill gives the agent specialized workflows. every tool returns a string because that's what goes back into the conversation.

tool definitions

the llm doesn't see your functions. it sees json schemas that describe what tools are available and what arguments they accept:

const defs = [
  { name: 'bash',  description: 'run bash cmd', parameters: mkp('command') },
  { name: 'read',  description: 'read a file',  parameters: mkp('path') },
  { name: 'write', description: 'write a file', parameters: mkp('path', 'content') },
].map(f => ({ type: 'function', function: f }));

mkp is a helper that builds a json schema object from a list of key names. each key becomes a required string property. the defs array is sent along with every api call so the model knows what it can do.

messages

the conversation is a flat array of message objects. each message has a role (system, user, assistant, or tool) and content. this array is the agent's entire memory:

const hist = [{ role: 'system', content: SYSTEM }];

// user says something
hist.push({ role: 'user', content: 'fix the bug in server.js' });

// assistant replies (pushed inside the loop)
// tool results get pushed too (role: 'tool')

the system message sets the agent's personality and context (working directory, date). every user message, assistant response, and tool result gets appended. the model sees the full history on each call, which is how it maintains context across multiple tool uses.

the api call

each iteration makes a single call to the chat completions endpoint. the model receives the full message history and the tool definitions:

const r = await fetch(`${base}/v1/chat/completions`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
  body: JSON.stringify({ model, messages: msgs, tools: defs }),
}).then(r => r.json());
const msg = r.choices[0].message;

the response message either has content (a text reply to the user) or tool_calls (the model wants to use tools). this is the decision point that drives the whole loop.

the agentic loop

this is the core of the harness. it's a while (true) that keeps calling the llm until it responds with text instead of tool calls:

async function run(msgs) {
  while (true) {
    const msg = await callLLM(msgs);  // make the api call
    msgs.push(msg);                   // add assistant response to history
    if (!msg.tool_calls) return msg.content;  // no tools? we're done
    // otherwise, execute tools and continue...
  }
}

the loop exits only when the model decides it has enough information to respond directly. the model might call tools once or twenty times, it drives its own execution. this is what makes it agentic: the llm decides when it's done, not the code.

tool execution

when the model returns tool_calls, the harness executes each one and pushes the result back into the message history as a tool message:

for (const t of msg.tool_calls) {
  const { name } = t.function;
  const args = JSON.parse(t.function.arguments);
  const result = String(await tools[name](args));
  msgs.push({ role: 'tool', tool_call_id: t.id, content: result });
}

each tool result is tagged with the tool_call_id so the model knows which call it corresponds to. after all tool results are pushed, the loop goes back to the top and calls the llm again, now with the tool outputs in context.

the repl

the outer shell is a simple read-eval-print loop. it reads user input, pushes it as a user message, calls run(), and prints the result:

while (true) {
  const input = await ask('\n> ');
  if (input.trim()) {
    hist.push({ role: 'user', content: input });
    console.log(await run(hist));
  }
}

there's also a one-shot mode (-p 'prompt') that skips the repl and exits after a single run. both modes use the same run() function. the agentic loop doesn't care where the prompt came from.

putting it together

the full flow looks like this:

user prompt → [system, user] → llm → tool_calls? → execute tools → [tool results] → llm → ... → text response

more sophisticated agents add things like memory, retries, parallel tool calls, or multi-agent delegation, but the core is always: loop, call, check for tools, execute, repeat.

Keywords

ai

FAQs

Package last updated on 21 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts