4a9675d242
- Inline self-contained <style> blocks in both templates (after ../style.css link, before </head>) - Gradient post titles (amber #c9935a -> warm gold #f59e0b -> blue #38bdf8) via background-clip: text - Date pills: monospace, blue text, rounded pill border - Code blocks: dark panel (#0a0e14), 1px border (#2a3340), rounded, monospace, overflow-x - Inline code: distinct background (#1c2230), green tint (#9fe6c0) - H2 left accent bar (#c9935a), H3 purple accent (#a855f7) - Blockquote callout rules (future-proofing; build.js doesn't emit <blockquote> yet) - Links: blue (#38bdf8), hover purple (#a855f7) - Index template: gradient title, section-label kicker pill, date pill, card hover accent - Nav/footer/site chrome untouched - 11 posts + index regenerated via node build.js
365 lines
19 KiB
HTML
365 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<title>Building a Cloud Agent Harness with DeepSeek V4 and Pi — Tinqs Blog</title>
|
||
<meta name="description" content="We forked Pi, merged a browser dashboard into the monorepo, and built a Go orchestrator inside our Gitea fork. Agents code overnight for about $0.80 — and you can watch them from localhost:33634.">
|
||
<meta name="robots" content="index, follow">
|
||
<link rel="canonical" href="https://www.tinqs.com/blog/cloud-harness">
|
||
|
||
<meta property="og:type" content="article">
|
||
<meta property="og:url" content="https://www.tinqs.com/blog/cloud-harness">
|
||
<meta property="og:title" content="Building a Cloud Agent Harness with DeepSeek V4 and Pi">
|
||
<meta property="og:description" content="Pi fork, merged agent dashboard, and a Go orchestrator inside Tinqs Studio.">
|
||
<meta property="og:image" content="https://www.tinqs.com/blog/img/cloud-harness-architecture.png">
|
||
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="twitter:title" content="Building a Cloud Agent Harness with DeepSeek V4 and Pi">
|
||
<meta name="twitter:description" content="Pi fork, merged agent dashboard, and a Go orchestrator inside Tinqs Studio.">
|
||
<meta name="twitter:image" content="https://www.tinqs.com/blog/img/cloud-harness-architecture.png">
|
||
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "BlogPosting",
|
||
"headline": "Building a Cloud Agent Harness with DeepSeek V4 and Pi",
|
||
"datePublished": "2026-05-26",
|
||
"author": {
|
||
"@type": "Person",
|
||
"name": "Ozan Bozkurt"
|
||
},
|
||
"publisher": {
|
||
"@type": "Organization",
|
||
"name": "Tinqs Limited",
|
||
"url": "https://www.tinqs.com"
|
||
},
|
||
"description": "We forked Pi, merged a browser dashboard into the monorepo, and built a Go orchestrator inside our Gitea fork. Agents code overnight for about $0.80 — and you can watch them from localhost:33634."
|
||
}
|
||
</script>
|
||
|
||
<!-- PostHog (EU) -->
|
||
<script>
|
||
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
||
posthog.init('phc_teG6p5oxf6poQHPThq5AGKzWQNhw4bHW9arLwWAVXm3f',{api_host:'https://eu.i.posthog.com',ui_host:'https://eu.posthog.com',person_profiles:'identified_only',defaults:'2026-01-30'})
|
||
</script>
|
||
|
||
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="../style.css">
|
||
<style>
|
||
/* ── Team guide aesthetic: self-contained overrides ── */
|
||
|
||
/* ── Gradient title (amber → warm gold, hint of blue) ── */
|
||
.post__title {
|
||
background: linear-gradient(90deg, #c9935a, #f59e0b 40%, #38bdf8);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
font-weight: 800;
|
||
}
|
||
|
||
/* ── Date pill ── */
|
||
.post__date {
|
||
display: inline-block;
|
||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
|
||
font-size: 0.72rem;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: #38bdf8;
|
||
border: 1px solid rgba(147, 140, 129, 0.25);
|
||
border-radius: 999px;
|
||
padding: 4px 14px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* ── Lead ── */
|
||
.post__lead {
|
||
color: #9aa7b4;
|
||
font-size: 1.08rem;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
/* ── H2: left accent bar ── */
|
||
.post__body h2 {
|
||
font-size: 1.7rem;
|
||
margin: 54px 0 6px;
|
||
padding-left: 16px;
|
||
border-left: 4px solid #c9935a;
|
||
}
|
||
|
||
/* ── H3: purple secondary accent ── */
|
||
.post__body h3 {
|
||
color: #a855f7;
|
||
font-size: 1.18rem;
|
||
margin: 30px 0 4px;
|
||
}
|
||
|
||
/* ── Inline code ── */
|
||
.post__body code {
|
||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
|
||
font-size: 0.86em;
|
||
background: #1c2230;
|
||
color: #9fe6c0;
|
||
padding: 2px 6px;
|
||
border-radius: 5px;
|
||
border: 1px solid #2a3340;
|
||
}
|
||
|
||
/* ── Code blocks (dark panel) ── */
|
||
.post__body pre {
|
||
background: #0a0e14;
|
||
border: 1px solid #2a3340;
|
||
border-radius: 10px;
|
||
padding: 16px 18px;
|
||
overflow-x: auto;
|
||
margin: 14px 0;
|
||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
|
||
font-size: 0.85rem;
|
||
line-height: 1.55;
|
||
color: #e6edf3;
|
||
}
|
||
|
||
/* Reset inline-code double-up inside pre */
|
||
.post__body pre code {
|
||
background: transparent;
|
||
padding: 0;
|
||
border: none;
|
||
font-size: inherit;
|
||
color: inherit;
|
||
border-radius: 0;
|
||
}
|
||
|
||
/* ── Blockquote callout (ready for future use; build.js does not emit blockquote yet) ── */
|
||
.post__body blockquote {
|
||
background: rgba(245, 158, 11, 0.08);
|
||
border: 1px solid rgba(245, 158, 11, 0.25);
|
||
border-left: 4px solid #f59e0b;
|
||
border-radius: 0 12px 12px 0;
|
||
padding: 16px 18px;
|
||
margin: 18px 0;
|
||
color: #f4e3c4;
|
||
font-size: 0.94rem;
|
||
}
|
||
|
||
/* ── Links ── */
|
||
.post__body a {
|
||
color: #38bdf8;
|
||
}
|
||
|
||
.post__body a:hover {
|
||
color: #a855f7;
|
||
}
|
||
|
||
/* ── Strong ── */
|
||
.post__body strong {
|
||
color: #f59e0b;
|
||
}
|
||
|
||
/* ── HR ── */
|
||
.post__body hr {
|
||
border: none;
|
||
border-top: 1px solid #2a3340;
|
||
margin: 32px 0;
|
||
}
|
||
|
||
/* ── Figures ── */
|
||
.post__body figure img {
|
||
border-radius: 12px;
|
||
border: 1px solid #2a3340;
|
||
}
|
||
|
||
.post__body figcaption {
|
||
color: #9aa7b4;
|
||
font-size: 0.85rem;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
/* ── List spacing ── */
|
||
.post__body li {
|
||
margin: 4px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- NAV -->
|
||
<nav class="nav nav--scrolled" id="nav">
|
||
<a href="/" class="nav__logo" aria-label="Tinqs home">
|
||
<span class="nav__wordmark">TINQS</span>
|
||
</a>
|
||
<div class="nav__links">
|
||
<a href="/#game" class="nav__link">Games</a>
|
||
<a href="/#tech" class="nav__link">Technology</a>
|
||
<a href="/#about" class="nav__link">About</a>
|
||
<a href="/blog/" class="nav__link" style="color: var(--c-accent-l);">Blog</a>
|
||
<a href="/#signup" class="nav__link">Contact</a>
|
||
<a href="/press" class="nav__link">Press</a>
|
||
</div>
|
||
<button class="nav__burger" aria-label="Open menu" id="navBurger">
|
||
<span></span><span></span><span></span>
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- MOBILE MENU -->
|
||
<div class="mobile-menu" id="mobileMenu">
|
||
<a href="/#game" class="mobile-menu__link">Games</a>
|
||
<a href="/#tech" class="mobile-menu__link">Technology</a>
|
||
<a href="/#about" class="mobile-menu__link">About</a>
|
||
<a href="/blog/" class="mobile-menu__link">Blog</a>
|
||
<a href="/#signup" class="mobile-menu__link">Contact</a>
|
||
<a href="/press" class="mobile-menu__link">Press</a>
|
||
</div>
|
||
|
||
<!-- POST -->
|
||
<article class="post">
|
||
<a href="/blog/" class="post__back">← All Posts</a>
|
||
<span class="post__date">26 May 2026</span>
|
||
<h1 class="post__title">Building a Cloud Agent Harness with DeepSeek V4 and Pi</h1>
|
||
<p class="post__lead">We spent a few sessions building something that still barely exists elsewhere: a cloud agent harness where AI coding agents are first-class citizens of the platform, not bolt-on tools. The stack is a <a href="https://tinqs.com/tinqs/pi" style="color: var(–c-accent-l);">Pi fork</a> for the brain, a Go orchestrator inside our <a href="https://tinqs.com/tinqs/studio" style="color: var(–c-accent-l);">Gitea fork</a> for overnight work, and a browser dashboard merged into Pi for the daytime. Here is how it fits together.</p>
|
||
|
||
<div class="post__body">
|
||
<h2>The Problem</h2>
|
||
<p>Every coding agent today — Claude Code, Codex, Pi, Aider — runs in your terminal. You watch it work. You close the laptop, it stops. There is no way to say "build these eight features overnight" and wake up to pull requests.</p>
|
||
<p>We wanted exactly that. Not a coding assistant. An autonomous workforce — with a UI when a human needs to be in the loop.</p>
|
||
<h2>Why Not Just Use Claude Code or Codex?</h2>
|
||
<p><strong>Cost.</strong> Claude Code runs on Opus at $15/MTok output. Codex uses GPT 5.5. Running eight agents overnight on either would cost $50–200. DeepSeek V4 Flash costs $0.28/MTok output. Eight overnight tasks: <strong>about $0.80</strong>.</p>
|
||
<p><strong>Control.</strong> Cloud tools are black boxes. We cannot add a Gitea API tool, a fal.ai image generator, or a guardrail that blocks <code>aws ec2 terminate-instances</code>. With our own harness, we add an extension and it is live.</p>
|
||
<p><strong>Platform.</strong> We are building <a href="https://tinqs.com" style="color: var(–c-accent-l);">Tinqs Studio</a> — a Gitea-based game development platform. Agents are not a feature we want to outsource. They are the product.</p>
|
||
<h2>Pi — The Agent Brain</h2>
|
||
<p><a href="https://pi.dev" style="color: var(–c-accent-l);">Pi</a> is an open-source coding agent by Mario Zechner. MIT license, TypeScript, minimal by design — four core tools (read, write, edit, bash) and an extension system.</p>
|
||
<p>We <a href="https://tinqs.com/tinqs/pi" style="color: var(–c-accent-l);">forked it</a>. Not to rewrite the core — to add first-party extensions:</p>
|
||
<ul>
|
||
<li><strong>tinqs-provider</strong> — routes DeepSeek V4 Flash and Pro through our inference proxy</li>
|
||
<li><strong>tinqs-tools</strong> — Gitea REST API, fal.ai image generation, Amazon Nova Lite vision</li>
|
||
<li><strong>tinqs-ci</strong> — reads CI pipeline status, logs, and polls for completion</li>
|
||
<li><strong>tinqs-guardrail</strong> — 29 safety patterns that block dangerous operations</li>
|
||
</ul>
|
||
<p>Each extension is a single TypeScript file. No extra npm dependencies on the extension side.</p>
|
||
<p>Pi has four output modes. The one that matters for automation is <strong>RPC</strong> — a headless process that accepts JSON on stdin/stdout. That is how the orchestrator drives it.</p>
|
||
<h2>DeepSeek V4 — The LLM</h2>
|
||
<p>DeepSeek V4 Flash through our own inference proxy. OpenAI-compatible API, so Pi treats it like any other provider. The proxy adds:</p>
|
||
<ul>
|
||
<li>Redis job queue (10 concurrent workers)</li>
|
||
<li>Per-user usage tracking</li>
|
||
<li>System prompt injection for cache hit optimization</li>
|
||
<li>Gitea PAT authentication (same token as git push)</li>
|
||
</ul>
|
||
<p>Cost per task: <strong>$0.02–0.10</strong> depending on complexity.</p>
|
||
<h2>Go Orchestrator — Overnight Batch Work</h2>
|
||
<p>Inside <code>tinqs/studio</code> we added <code>modules/agents/</code> — a Go worker pool that:</p>
|
||
<ul>
|
||
<li>Spawns Pi with <code>–mode rpc –no-session</code></li>
|
||
<li>Tracks task lifecycle (pending → running → done)</li>
|
||
<li>Streams events over <strong>SSE</strong> to any connected UI</li>
|
||
<li>Enforces guardrails at the platform layer (worker limits, timeouts)</li>
|
||
</ul>
|
||
<p>Six HTTP endpoints, same auth as git push:</p>
|
||
<pre><code>POST /api/v1/agents/tasks — submit a task
|
||
GET /api/v1/agents/tasks — list all tasks
|
||
GET /api/v1/agents/tasks/{id} — get task details
|
||
DELETE /api/v1/agents/tasks/{id} — stop a task
|
||
GET /api/v1/agents/stream — SSE live events
|
||
GET /api/v1/agents/health — orchestrator status</code></pre>
|
||
<p>We considered bolting on a separate orchestration SaaS and rejected it. The orchestrator lives in the same binary as git — same auth, no extra service to deploy.</p>
|
||
<p>The intended loop:</p>
|
||
<pre><code>Orchestrator reads task brief
|
||
→ spawns pi --mode rpc
|
||
→ Pi writes code using DeepSeek V4
|
||
→ Pi pushes branch, calls ci_wait
|
||
→ CI green → Pi opens PR via gitea_api
|
||
→ CI red → Pi reads ci_logs, fixes, retries
|
||
→ Human reviews PR, merges</code></pre>
|
||
<p>Git worktree integration and full push/PR automation are still being wired; the API and worker pool already run locally.</p>
|
||
<h2>Pi Dashboard — Browser UI (Shipped)</h2>
|
||
<p>The cloud orchestrator is for batch work while you sleep. During the day you want to see agents, chat with them, and spawn sessions without living in a terminal.</p>
|
||
<p>We merged <a href="https://github.com/BlackBeltTechnology/pi-agent-dashboard" style="color: var(–c-accent-l);">pi-agent-dashboard</a> into the Pi monorepo — not as a second repo to install. One checkout, one command:</p>
|
||
<pre><code class="language-bash">npm run dashboard:dev</code></pre>
|
||
<p>Open <strong>http://localhost:33634</strong>. You get:</p>
|
||
<ul>
|
||
<li><strong>Live session streaming</strong> — watch tool calls and model output in real time</li>
|
||
<li><strong>Interactive chat</strong> — send prompts, answer <code>ask_user</code> dialogs from the browser</li>
|
||
<li><strong>Session spawning</strong> — start Pi in any pinned project folder</li>
|
||
<li><strong>Cost tracking</strong> — per-session token usage when using Tinqs inference</li>
|
||
<li><strong>Plugins</strong> — flows, subagents, workspace helpers</li>
|
||
</ul>
|
||
<p>The dashboard talks to Pi sessions over a WebSocket bridge on port <strong>9999</strong>. Inference uses the same Tinqs proxy as the CLI — register a custom provider in <code>~/.pi/agent/providers.json</code> and authenticate with your existing <code>tstudio</code> token. No separate LLM API keys.</p>
|
||
<pre><code>Dashboard (localhost:33634)
|
||
↕ WebSocket (port 9999)
|
||
Pi sessions (interactive or headless)
|
||
↕ OpenAI-compatible API
|
||
Tinqs Studio proxy (tinqs.com/api/v1/ai)
|
||
↕ DeepSeek V4 Flash / Pro</code></pre>
|
||
<p>When Studio runs locally with agents enabled, the dashboard can also talk to the orchestrator API on port 3000 — submit tasks and watch SSE events in the same UI.</p>
|
||
<p>One browser tab for daytime work; the orchestrator queue for overnight runs.</p>
|
||
<h2>The Guardrail</h2>
|
||
<p>Our biggest fear: an agent hallucinating instead of using tools, or running <code>aws ec2 terminate-instances</code> at 3 AM.</p>
|
||
<p>The guardrail extension monitors every agent turn:</p>
|
||
<p><strong>Hallucination detection</strong> — if the agent claims file contents without calling <code>read</code>, it gets corrected.</p>
|
||
<p><strong>No-tool drift</strong> — three consecutive turns without a tool call triggers a warning.</p>
|
||
<p><strong>Command blocking</strong> — 29 patterns covering destructive git, AWS teardown, process killing, and production API abuse.</p>
|
||
<h2>What It Cost to Build</h2>
|
||
<p>A few focused sessions: about 2,000 lines of Go, 900 lines of TypeScript extensions, 52 tests, plus merging the dashboard packages into the Pi monorepo. No new servers — Pi is a Node subprocess; the dashboard is another Node process on your machine.</p>
|
||
<h2>What Is Next</h2>
|
||
<p>| Piece | Status |</p>
|
||
<p>|——-|——–|</p>
|
||
<p>| Pi fork + tinqs extensions | Shipped |</p>
|
||
<p>| Dashboard merged into Pi monorepo | Shipped |</p>
|
||
<p>| Go orchestrator + REST/SSE API | MVP, running locally |</p>
|
||
<p>| Git worktree + push + PR loop | In progress |</p>
|
||
<p>| Domain routing (game / sim / platform tasks) | Designed |</p>
|
||
<p>Next we are promoting studio skills from IDE playbooks into orchestrator prompt packs — so the same Pi worker behaves like a game builder, sim maintainer, or platform engineer depending on the task. Specialized agents (planner, reviewer, asset pipeline) sit on top of this foundation.</p>
|
||
<p>The harness — inference proxy, guardrails, dashboard, orchestrator API — is in place. The work now is feeding it real tasks and hardening the git loop.</p>
|
||
<hr>
|
||
<p><em>Tinqs Studio is an open platform for game development — git hosting, AI inference, asset generation, and autonomous agents. We are building <a href="https://arikigame.com" style="color: var(–c-accent-l);">Ariki</a>, a survival colony sim, using the same tools we ship.</em></p>
|
||
|
||
</div>
|
||
|
||
<div class="post__author">
|
||
<div class="post__author-avatar">OB</div>
|
||
<div class="post__author-info">
|
||
<span class="post__author-name">Ozan Bozkurt</span><br>
|
||
CTO & Developer, Tinqs
|
||
</div>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- FOOTER -->
|
||
<footer class="footer">
|
||
<div class="footer__inner">
|
||
<span class="footer__wordmark">TINQS</span>
|
||
<div class="footer__links">
|
||
<a href="/#game">Games</a>
|
||
<a href="/#tech">Technology</a>
|
||
<a href="/#about">About</a>
|
||
<a href="/blog/">Blog</a>
|
||
<a href="mailto:hello@tinqs.com">hello@tinqs.com</a>
|
||
<a href="/press">Press Kit</a>
|
||
</div>
|
||
<p class="footer__copy">Tinqs Limited — London, est. 2020</p>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
const burger = document.getElementById('navBurger');
|
||
const mobileMenu = document.getElementById('mobileMenu');
|
||
burger.addEventListener('click', () => {
|
||
const open = mobileMenu.classList.toggle('mobile-menu--open');
|
||
burger.classList.toggle('nav__burger--open', open);
|
||
document.body.style.overflow = open ? 'hidden' : '';
|
||
});
|
||
mobileMenu.querySelectorAll('a').forEach(link => {
|
||
link.addEventListener('click', () => {
|
||
mobileMenu.classList.remove('mobile-menu--open');
|
||
burger.classList.remove('nav__burger--open');
|
||
document.body.style.overflow = '';
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|