<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arun Rajiah</title>
    <description>The latest articles on DEV Community by Arun Rajiah (@arunrajiah_11).</description>
    <link>https://dev.to/arunrajiah_11</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909239%2Fe43ee18d-ec31-4f41-866b-f7ff39575213.jpeg</url>
      <title>DEV Community: Arun Rajiah</title>
      <link>https://dev.to/arunrajiah_11</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arunrajiah_11"/>
    <language>en</language>
    <item>
      <title>How I built an open-source AI coworker for founders — provider-agnostic, persistent memory, full-stack TypeScript</title>
      <dc:creator>Arun Rajiah</dc:creator>
      <pubDate>Thu, 11 Jun 2026 08:56:14 +0000</pubDate>
      <link>https://dev.to/arunrajiah_11/how-i-built-an-open-source-ai-coworker-for-founders-provider-agnostic-persistent-memory-4hka</link>
      <guid>https://dev.to/arunrajiah_11/how-i-built-an-open-source-ai-coworker-for-founders-provider-agnostic-persistent-memory-4hka</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every founder I know has the same complaint about AI tools: they're stateless. You explain your business to ChatGPT, close the tab, and tomorrow you start over. There's no "coworker" — just a very smart amnesiac.&lt;/p&gt;

&lt;p&gt;I wanted to fix that. Not with a wrapper around ChatGPT, but with a proper application: persistent memory, task management, multi-channel, autopilot, and — critically — zero vendor lock-in on the AI provider.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/arunrajiah/coworker" rel="noopener noreferrer"&gt;Coworker&lt;/a&gt;&lt;/strong&gt; — an open-source AI coworker for founders that you can self-host in one command.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture decision that drove everything else
&lt;/h2&gt;

&lt;p&gt;The first real decision was how to handle LLM routing. I'd been watching &lt;strong&gt;&lt;a href="https://github.com/andrewyng/aisuite" rel="noopener noreferrer"&gt;aisuite&lt;/a&gt;&lt;/strong&gt; by Andrew Ng — a Python library that gives you a unified &lt;code&gt;provider:model&lt;/code&gt; API across OpenAI, Anthropic, Google, and 17 more providers. The ergonomics are excellent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aisuite — Python
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic:claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wanted the same idea in TypeScript, plus two things aisuite doesn't have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automatic fallback chains&lt;/strong&gt; — if the primary key is missing or the provider is down, try the next one automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The full application layer&lt;/strong&gt; — persistent memory, task queue, multi-tenant DB, UI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what our &lt;code&gt;provider.ts&lt;/code&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Supports "provider:model" string syntax just like aisuite&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseModelString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ProviderConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colonIdx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;modelString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colonIdx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelString&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ProviderName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colonIdx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ProviderName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colonIdx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Automatic fallback: if Anthropic key missing, try OpenAI, then Groq&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildLLMProviderWithFallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProviderConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;LLMProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[])]&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;buildLLMProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All providers in fallback chain failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your &lt;code&gt;.env&lt;/code&gt;, you can now write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LLM_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic:claude-sonnet-4-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And switch the entire workspace to a different model in the UI without touching code or redeploying.&lt;/p&gt;

&lt;p&gt;We support 11 providers: Anthropic, OpenAI, Google, Groq, Mistral, xAI (Grok), Cohere, DeepSeek, Together AI, OpenRouter (200+ models via one key), and Ollama for fully local models.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 15 App Router&lt;/td&gt;
&lt;td&gt;Streaming, RSC, fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Hono + Node.js&lt;/td&gt;
&lt;td&gt;Type-safe, composable middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent runtime&lt;/td&gt;
&lt;td&gt;Vercel AI SDK&lt;/td&gt;
&lt;td&gt;Provider-agnostic, tool use, streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Job queue&lt;/td&gt;
&lt;td&gt;BullMQ + Redis&lt;/td&gt;
&lt;td&gt;Durable agent runs, cron autopilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL 16 + pgvector&lt;/td&gt;
&lt;td&gt;Relational + semantic memory in one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM&lt;/td&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;SQL-first, type-safe, migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The memory problem
&lt;/h2&gt;

&lt;p&gt;Stateless AI is useless as a coworker. But naive memory (dump the full conversation history) hits context limits fast and costs a lot.&lt;/p&gt;

&lt;p&gt;We solved it with &lt;strong&gt;pgvector semantic retrieval&lt;/strong&gt;: every message is embedded and stored. On each new turn, we retrieve the top-K most semantically similar memories by cosine distance and inject them into the system prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retrieve relevant memories before each agent run&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recentMemories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retrieveRelevantMemories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// They land in the system prompt automatically&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;templateType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;activeSkills&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;recentMemories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- injected here&lt;/span&gt;
  &lt;span class="nx"&gt;openTasksSummary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: you can tell your coworker "our SaaS targets mid-market legal firms" in January and it will remember that context in June, without you having to repeat it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Durability via BullMQ
&lt;/h2&gt;

&lt;p&gt;Every agent run is a BullMQ job, not an HTTP request. This matters because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM calls can take 30+ seconds — you don't want a dropped connection to kill a job&lt;/li&gt;
&lt;li&gt;Autopilot cron runs need to survive process restarts&lt;/li&gt;
&lt;li&gt;Tool calls (create task, search tasks, trigger deploy) need exactly-once semantics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The executor loop runs up to 10 tool-call steps per turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;llmMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search_tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;list_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plan_work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;list_issues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;create_issue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;list_pull_requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;list_vercel_connections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger_deployment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;maxSteps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onStepFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Broadcast tool-in-progress to UI via Redis pub/sub → WebSocket&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ws:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;workspaceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent:tool_call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI shows a live "Searching tasks…" indicator while the agent works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Founder templates
&lt;/h2&gt;

&lt;p&gt;Different businesses have different vocabularies. A SaaS founder doesn't think in the same terms as an agency owner.&lt;/p&gt;

&lt;p&gt;We ship 7 templates that pre-configure the system prompt with domain-specific context:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;What the agent understands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;MRR, churn, activation, sprint planning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agency&lt;/td&gt;
&lt;td&gt;Client retainers, utilization, proposals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecommerce&lt;/td&gt;
&lt;td&gt;AOV, ROAS, inventory, campaigns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consulting&lt;/td&gt;
&lt;td&gt;Engagements, deliverables, pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freelancer&lt;/td&gt;
&lt;td&gt;Projects, invoices, deadlines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creator&lt;/td&gt;
&lt;td&gt;Subscribers, views, sponsorships, content calendar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real Estate&lt;/td&gt;
&lt;td&gt;Listings, GCI, pipeline, buyer/seller leads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What you get out of the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kanban board&lt;/strong&gt; with drag-and-drop (Backlog → Todo → In Progress → Review → Done)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent threads&lt;/strong&gt; — every conversation is remembered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File attachments&lt;/strong&gt; — PDFs, images, text files read in context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autopilot&lt;/strong&gt; — scheduled agent runs (daily briefings, weekly wrap-ups)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations&lt;/strong&gt; — GitHub/GitLab/Bitbucket, Vercel, Slack, WhatsApp (Twilio), Telegram&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-user workspaces&lt;/strong&gt; with RLS at the database level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; — custom instructions with trigger phrases (e.g. &lt;code&gt;/standup&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dark mode&lt;/strong&gt;, ⌘K new chat, real-time tool-in-progress indicator&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/arunrajiah/coworker
&lt;span class="nb"&gt;cd &lt;/span&gt;coworker
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env

&lt;span class="c"&gt;# Add at least one LLM key&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ANTHROPIC_API_KEY=sk-ant-..."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .env
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AUTH_SECRET=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .env

docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; infra/docker/docker-compose.oss.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
open http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Postgres, Redis, API, worker, and web — all wired up.&lt;/p&gt;




&lt;h2&gt;
  
  
  vs. aisuite
&lt;/h2&gt;

&lt;p&gt;I want to be clear: aisuite and Coworker are &lt;strong&gt;complementary, not competing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;aisuite is a routing library — clean, minimal, exactly what it says it is. If you're building a Python LLM application and need to swap providers without rewriting code, use aisuite.&lt;/p&gt;

&lt;p&gt;Coworker is an application — it solves everything above the routing layer: where do responses live, how do you retrieve relevant context, how do you schedule runs, how do you give the agent tools, how do you handle multiple users.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;aisuite&lt;/th&gt;
&lt;th&gt;Coworker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-provider LLM routing&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;provider:model&lt;/code&gt; string syntax&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic model fallback chains&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent memory (pgvector RAG)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task management + Kanban&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autopilot scheduling&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack / WhatsApp / Telegram&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-user + RLS&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hostable&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;aisuite solves routing. Coworker solves working.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider health UI&lt;/strong&gt; — status badge per provider in settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side-by-side model comparison&lt;/strong&gt; — send the same prompt to two models simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linear, Notion, Google Calendar&lt;/strong&gt; integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building something similar in Python and want to compare notes on the memory architecture or the BullMQ job design, drop a comment. And if you're a founder who wants to try it, the Docker setup takes about 3 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo: &lt;a href="https://github.com/arunrajiah/coworker" rel="noopener noreferrer"&gt;https://github.com/arunrajiah/coworker&lt;/a&gt;&lt;/strong&gt; — a star means a lot at this stage.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Your team uses Odoo — without logging in to Odoo: Introducing OdooPilot</title>
      <dc:creator>Arun Rajiah</dc:creator>
      <pubDate>Sat, 02 May 2026 16:23:17 +0000</pubDate>
      <link>https://dev.to/arunrajiah_11/your-team-uses-odoo-without-logging-in-to-odoo-introducing-odoopilot-470d</link>
      <guid>https://dev.to/arunrajiah_11/your-team-uses-odoo-without-logging-in-to-odoo-introducing-odoopilot-470d</guid>
      <description>&lt;p&gt;Your warehouse manager shouldn't need to learn Odoo to update a stock count. Your sales rep shouldn't need a VPN to log a call note. Your HR assistant shouldn't need the Odoo UI to approve a leave request.&lt;/p&gt;

&lt;p&gt;They just need to send a message.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;strong&gt;OdooPilot&lt;/strong&gt; solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OdooPilot?
&lt;/h2&gt;

&lt;p&gt;OdooPilot is an open-source Odoo 17 Community addon (v17.0.11.0.0) that gives every employee an AI assistant on &lt;strong&gt;Telegram or WhatsApp&lt;/strong&gt; — connected to the same Odoo instance, scoped to the same permissions they already have.&lt;/p&gt;

&lt;p&gt;No login. No training. No extra server. No Docker. No SaaS fees. Everything runs inside your Odoo instance.&lt;/p&gt;

&lt;p&gt;Here's what it looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mira (WhatsApp):   "I need 3 days off next month — Mar 14–16."
OdooPilot:         "Filed leave request for 3 days (Mar 14–16). Carlos has been notified."

Carlos (Telegram): [inline button: ✅ Approve   ❌ Refuse]
Carlos:            taps Approve.
OdooPilot:         "✅ Leave approved. Mira has been notified."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Odoo adoption problem solved: data is no longer stale because the people who generate it finally have a way to reach Odoo that fits their day. Same data, same permissions, same audit trail — just lower friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical architecture
&lt;/h2&gt;

&lt;p&gt;Everything runs &lt;strong&gt;inside the Odoo addon&lt;/strong&gt; — no separate Python service, no cloud deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Controllers&lt;/strong&gt; — verify webhook HMAC signatures in constant time, per-(channel, chat_id) sliding-window rate limit, idempotency dedup on message IDs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent loop&lt;/strong&gt; — loads session, builds messages, runs the LLM tool loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read tools&lt;/strong&gt; execute immediately; &lt;strong&gt;Write tools&lt;/strong&gt; → preflight → resolve target record → stage pending args + per-write nonce → ask Yes/No&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On confirmed Yes&lt;/strong&gt; → execute under the linked Odoo user's environment (all record rules apply)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  LLM support — swap in Settings, no restart needed
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Default model&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anthropic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-3-5-haiku-20241022&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Best reasoning per dollar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-4o-mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Widest ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;groq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama-3.3-70b-versatile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Free tier, very fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ollama&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(set in config)&lt;/td&gt;
&lt;td&gt;100% local — data never leaves your server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Security (passed public audit April 2026)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Webhook HMAC-SHA256 verification in constant time — mandatory for both Telegram and WhatsApp&lt;/li&gt;
&lt;li&gt;Per-write nonce — the confirmation click is cryptographically bound to the exact staged write; prompt injection can't swap it&lt;/li&gt;
&lt;li&gt;SHA-256 hashed magic-link tokens, single-use, 1-hour expiry&lt;/li&gt;
&lt;li&gt;Two-step CSRF-protected account linking flow&lt;/li&gt;
&lt;li&gt;Bounded thread pool + sliding-window rate limiting&lt;/li&gt;
&lt;li&gt;Immutable audit log for every tool call (timestamp, user, tool, args, result)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it covers today
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;th&gt;Write (with confirmation)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project &amp;amp; Tasks&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ mark task done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sales &amp;amp; CRM&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ confirm order · update stage · create lead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invoices &amp;amp; Accounting&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HR &amp;amp; Leaves&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ approve leave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Write tools always show an inline Yes/No confirmation before touching any data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community feedback wanted
&lt;/h2&gt;

&lt;p&gt;I'm building this for the Odoo community and I'd love your input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👉 Which modules do you need most? (Manufacturing? Timesheets? Point of Sale?)&lt;/li&gt;
&lt;li&gt;👉 Telegram or WhatsApp — which does your team actually use?&lt;/li&gt;
&lt;li&gt;👉 What's missing that would make you install it today?&lt;/li&gt;
&lt;li&gt;👉 If OdooPilot saves your team time, consider &lt;a href="https://github.com/sponsors/arunrajiah" rel="noopener noreferrer"&gt;sponsoring&lt;/a&gt; to keep development going!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;Odoo App Store&lt;/strong&gt;: &lt;a href="https://apps.odoo.com/apps/modules/17.0/odoopilot" rel="noopener noreferrer"&gt;https://apps.odoo.com/apps/modules/17.0/odoopilot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub&lt;/strong&gt; (LGPL-3 open-source): &lt;a href="https://github.com/arunrajiah/odoopilot" rel="noopener noreferrer"&gt;https://github.com/arunrajiah/odoopilot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;♥ &lt;strong&gt;Sponsor&lt;/strong&gt;: &lt;a href="https://github.com/sponsors/arunrajiah" rel="noopener noreferrer"&gt;https://github.com/sponsors/arunrajiah&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Current release: 17.0.11.0.0 — pure Odoo 17 Community addon, no external dependencies beyond what Odoo already ships.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>opensource</category>
      <category>aiops</category>
      <category>erp</category>
    </item>
  </channel>
</rss>
