taurusai
v0.1.2
Published
TaurusAI - a lightweight local AI agent harness/runtime built from scratch with Node.js, TypeScript, Express and the official OpenAI SDK.
Maintainers
Readme
TaurusAI
A lightweight local AI agent harness/runtime built from scratch in TypeScript.
TaurusAI starts a small HTTP server on http://localhost:8790 with:
- an AgentRegistry, SessionRegistry, ToolRegistry, and LLMProviderRegistry
- an AgentRunner that drives the LLM ↔ tool loop
- a REST API to list agents/tools/providers, create sessions, and send turns
The initial implementation talks directly to OpenAI via the official openai
npm SDK. There is no LangChain, LangGraph, LiteLLM, TrueForge, Semantic
Kernel, AutoGen, OpenRouter, or any other gateway or orchestration framework.
The point is to see how a tiny agent harness actually works end-to-end.
Architecture
npx taurusai
│
▼
CLI Bootstrap (src/cli.ts)
│
▼
Local Express Server
http://localhost:8790
│
▼
┌────────────────────────────────────┐
│ Agent Runtime │
│ │
│ AgentRegistry ToolRegistry │
│ SessionRegistry ProviderReg. │
│ AgentRunner (loop controller) │
└─────────────────┬──────────────────┘
│
▼
LLMProvider (interface)
│
▼
OpenAIProvider
│
▼
Official OpenAI SDK
│
▼
OpenAI APITool-execution path:
LLM → tool_call → AgentRunner → ToolRegistry
→ tool.execute(args) → Tool Result
→ AgentRunner → LLM (next iteration)Responsibilities
| Component | Responsibility |
|-----------------------|----------------------------------------------------------|
| AgentSpec | Describes an agent (model, prompt, tools, limits) |
| AgentRegistry | Stores/retrieves AgentSpecs |
| Session | A single conversation (messages, metadata) |
| SessionRegistry | CRUD for sessions (in-memory for now) |
| LLMProvider | Provider-neutral generate() interface |
| OpenAIProvider | OpenAI SDK adapter → normalized responses |
| LLMProviderRegistry | Lookup providers by name |
| ToolDefinition | Describes a tool (name, schema, execute) |
| ToolRegistry | Register / list / execute tools |
| AgentRunner | The loop: LLM ↔ tools until a final answer |
| CLI | Boot runtime, wire registries, start server |
| API Routes | REST surface for sessions / agents / tools / providers |
Prerequisites
- Node.js >= 18
- An OpenAI API key
Install & local dev
npm install
cp .env.example .env # on Windows PowerShell: copy .env.example .env
# edit .env and set OPENAI_API_KEY
npm run devThen in another terminal:
curl http://localhost:8790/healthTo build and run from compiled output:
npm run build
npm startREST API
| Method | Path | Description |
|--------|------------------------------------|---------------------------------------------|
| GET | /health | Health check |
| GET | /agents | List registered agents |
| GET | /tools | List registered tools |
| GET | /providers | List registered LLM providers |
| POST | /sessions | Create a session for an agent |
| GET | /sessions | List sessions |
| GET | /sessions/:sessionId | Get session + full message history |
| DELETE | /sessions/:sessionId | Delete a session |
| POST | /sessions/:sessionId/turns | Send a user input, run the agent loop |
End-to-end example
# 1. Create a session attached to the architecture agent
curl -s -X POST http://localhost:8790/sessions \
-H "Content-Type: application/json" \
-d '{"agentId":"architecture-agent"}'
# response: { "sessionId": "..." }
# 2. Send a turn that forces the model to call BOTH tools
curl -s -X POST http://localhost:8790/sessions/<sessionId>/turns \
-H "Content-Type: application/json" \
-d '{"input":"What is the weather in Bangalore and multiply the temperature by 2?"}'The harness will:
- Call the model with the question and both tool schemas.
- Model asks for
weather({city:"Bangalore"})→ harness returns27. - Call the model again with the tool result.
- Model asks for
calculator({a:27, b:2, operation:"multiply"})→ harness returns54. - Call the model one more time; model produces a final natural-language answer.
Server logs will look like:
[Harness] Iteration 1
[LLM] Provider: openai
[LLM] Model: gpt-4o-mini
[LLM] Tool requested: weather
[Tool] Executing weather
[Tool] Result received
[LLM] Calling model again
[Harness] Iteration 2
[LLM] Tool requested: calculator
[Tool] Executing calculator
[Tool] Result received
[LLM] Calling model again
[Harness] Iteration 3
[Harness] Final response receivedTesting the CLI locally before publishing
Option A — npm link
npm run build
npm link
taurusaiTo unlink later:
npm unlink -g taurusaiOption B — npm pack
npm run build
npm pack
# produces taurusai-0.1.0.tgz
npm install -g ./taurusai-0.1.0.tgz
taurusaiUninstall:
npm uninstall -g taurusaiPublishing to npm
Create an npm account at https://www.npmjs.com.
Login from the terminal:
npm loginVerify who you are:
npm whoamiBuild the TypeScript output:
npm run buildInspect what will actually be published:
npm publish --dry-runPublish:
npm publishIf using a scoped package name (e.g.
@santoshrbtx/taurusai), publish as public:npm publish --access publicVerify:
npm view taurusaiRun via npx:
npx taurusai # or for a scoped package: npx @santoshrbtx/taurusai
Versioning
npm version patch # 0.1.0 -> 0.1.1 (bug fixes)
npm version minor # 0.1.1 -> 0.2.0 (backwards-compatible features)
npm version major # 0.2.0 -> 1.0.0 (breaking changes)
npm publishSecurity
.envis in.gitignoreand is not included infilesinpackage.json, sonpm publishcannot leak it.dist/is.gitignored (build output), but is listed infilesinpackage.jsonso it is included in the published tarball.- API keys are never logged or exposed via any REST endpoint.
node_modules/,*.log, and*.tgzare also ignored.
Extending
- Add a tool: implement
ToolDefinitionand calltools.register(myTool)insrc/cli.ts. Add its name to the agent'stoolslist. - Add a provider: implement
LLMProvider, register it inLLMProviderRegistryasproviders.register("anthropic", new AnthropicProvider(...)), and point an agent at it viaprovider: "anthropic". TheAgentRunnercode does not change. - Persist sessions: swap the in-memory
MapinSessionRegistryfor Redis / PostgreSQL / DynamoDB. The method signatures stay the same.
License
MIT
