<?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: Gad Ofir</title>
    <description>The latest articles on DEV Community by Gad Ofir (@gad_ofir_076c468dd15d483b).</description>
    <link>https://dev.to/gad_ofir_076c468dd15d483b</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3880929%2Fdc7b95cc-8e4c-4ccb-af36-3896c39121e4.png</url>
      <title>DEV Community: Gad Ofir</title>
      <link>https://dev.to/gad_ofir_076c468dd15d483b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gad_ofir_076c468dd15d483b"/>
    <language>en</language>
    <item>
      <title>What I learned building an agent platform that actually ships</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Sun, 02 Aug 2026 04:50:05 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/what-i-learned-building-an-agent-platform-that-actually-ships-5ab7</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/what-i-learned-building-an-agent-platform-that-actually-ships-5ab7</guid>
      <description>&lt;p&gt;I built an agent platform. Not a demo — an actual platform where every tool call is governed, audited, and scoped to a workspace.&lt;/p&gt;

&lt;p&gt;Here's what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every skill is a first-class citizen
&lt;/h2&gt;

&lt;p&gt;The platform has &lt;strong&gt;27 skills&lt;/strong&gt; across 7 groups — platform ops, publishing (dev.to, LinkedIn), testing (a full TEA suite with risk matrices, baseline characterization, NFR audits), communication (Slack, Telegram, Gmail), knowledge (RAG + persistent memory), development, and scraping.&lt;/p&gt;

&lt;p&gt;Skills aren't just prompts. Each one has a governed lifecycle: create, validate, version, insert into workspaces. No stray scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  14 integrated services, one audit trail
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; (issues/PRs), &lt;strong&gt;Jira&lt;/strong&gt; (tickets), &lt;strong&gt;Slack&lt;/strong&gt; &amp;amp; &lt;strong&gt;Telegram&lt;/strong&gt; (messaging)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gmail&lt;/strong&gt;, &lt;strong&gt;Google Calendar&lt;/strong&gt;, &lt;strong&gt;Google Drive&lt;/strong&gt; (OAuth2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt; (profile + post publishing), &lt;strong&gt;Dev.to&lt;/strong&gt; (blog)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge Base&lt;/strong&gt; (RAG retrieval), &lt;strong&gt;Memory&lt;/strong&gt; (persistent cross-session)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Search&lt;/strong&gt;, &lt;strong&gt;Transcribe&lt;/strong&gt; (Whisper)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every outbound call goes through a &lt;code&gt;platform_cli&lt;/code&gt; dispatch op or a &lt;code&gt;service_call&lt;/code&gt; adapter — same audit pipe, same trace ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI is the source of truth
&lt;/h2&gt;

&lt;p&gt;You don't edit config files and hope. You verify with &lt;code&gt;loop_list&lt;/code&gt;, &lt;code&gt;policy_show&lt;/code&gt;, &lt;code&gt;skill_list&lt;/code&gt;, &lt;code&gt;service_list&lt;/code&gt;, &lt;code&gt;workflow_catalog&lt;/code&gt;. If you can't see it there, it doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing isn't an afterthought
&lt;/h2&gt;

&lt;p&gt;The TEA (Test Engineering Architecture) suite ships 6 workflows: test design (risk-matrix), baseline characterization (golden-master diffing), NFR evidence audits, automated test generation, test review, and requirement-to-coverage tracing. All output is committed JSON or markdown — no proprietary formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;Not everything is production-ready. 4 of 27 skills are blocked (missing services or out of workspace scope). The model is &lt;code&gt;deepseek-chat&lt;/code&gt; with &lt;code&gt;deepseek-reasoner&lt;/code&gt; fallback — pragmatic, not exotic. Workspace scratch files TTL at 24 hours. Risky commands (&lt;code&gt;kubectl delete&lt;/code&gt;, &lt;code&gt;helm uninstall&lt;/code&gt;) are pattern-blocked.&lt;/p&gt;

&lt;p&gt;But the core idea works: &lt;strong&gt;governed agent tools, scoped to a workspace, with real integrations, real memory, and real audit&lt;/strong&gt;. That's more than most agent frameworks deliver.&lt;/p&gt;

&lt;p&gt;If you're building agents that need to touch real APIs (not just generate text), consider: every integration should be a governed skill, every call should be traced, and you should be able to verify state by reading back from the platform — not by guessing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>platform</category>
      <category>devops</category>
    </item>
    <item>
      <title>A lint that crashes before it asserts is worse than no lint</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Sun, 02 Aug 2026 04:46:08 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/a-lint-that-crashes-before-it-asserts-is-worse-than-no-lint-1k0o</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/a-lint-that-crashes-before-it-asserts-is-worse-than-no-lint-1k0o</guid>
      <description>&lt;h1&gt;
  
  
  A lint that crashes before it asserts is worse than no lint
&lt;/h1&gt;

&lt;p&gt;We recently found a CI gate that had been reporting green for weeks without ever actually checking anything.&lt;/p&gt;

&lt;p&gt;The guard was a small Python test that enumerated tracked files with &lt;code&gt;git ls-files&lt;/code&gt; before running a set of per-file assertions. It worked perfectly on every developer machine and on most CI pods — until it ran inside a stripped-down test pod with no &lt;code&gt;git&lt;/code&gt; binary installed. &lt;code&gt;subprocess.run(["git", ...])&lt;/code&gt; raised &lt;code&gt;FileNotFoundError&lt;/code&gt; immediately, the process exited before a single assertion executed, and the caller treated the crash as "not applicable" and moved on. Total runtime: 63ms. The gate reported success.&lt;/p&gt;

&lt;p&gt;That's the trap: a test that dies before asserting is strictly worse than no test at all. No test is an honest gap you can see in your coverage. A test that crashes-then-reports-green is a gap disguised as a guarantee — it burns exactly the trust it's supposed to build, and nobody notices until something it should have caught ships anyway.&lt;/p&gt;

&lt;p&gt;The fix had two parts, and only one of them is the obvious one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a fallback path (&lt;code&gt;os.walk&lt;/code&gt; when &lt;code&gt;git&lt;/code&gt; is unavailable) so the check still runs in every environment.&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;non-empty guard&lt;/strong&gt;: if the enumerator returns zero files, fail loudly with a distinct message, instead of letting "zero files checked" look identical to "all files passed."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Part 2 is the one that actually matters. A fallback makes the happy path work again. A non-empty guard is what stops the &lt;em&gt;next&lt;/em&gt; environment mismatch from going vacuously green.&lt;/p&gt;

&lt;p&gt;If you own a CI gate: ask what happens when its setup step fails, not just when its assertions fail. Those are two different failure modes, and usually only one of them is tested.&lt;/p&gt;

&lt;p&gt;— notes from the agent platform team&lt;/p&gt;

</description>
      <category>testing</category>
      <category>cicd</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>ToolShell's Pillars for Agent Orchestration</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:41:01 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/toolshells-pillars-for-agent-orchestration-2616</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/toolshells-pillars-for-agent-orchestration-2616</guid>
      <description>&lt;p&gt;When I started wiring agents into my daily work, it went about as you'd expect: a flood of half-finished changes, conflicting edits across the same files, no shared memory of what anyone was supposed to be doing. The agents were capable. The orchestration was not.&lt;/p&gt;

&lt;p&gt;What follows is the set of pillars that stabilized it — not a tutorial, not a product pitch, and not something to copy. Take what fits, leave the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. God, King, Worker — shaped delegation, not a flat pool
&lt;/h2&gt;

&lt;p&gt;Flat agent pools — spin up N workers, throw tasks at them, collect results — break down when tasks have dependencies or need judgment. Some decisions need direction. Some need routing. Some just need raw execution.&lt;/p&gt;

&lt;p&gt;The shape that worked here is a three-role split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;God&lt;/strong&gt; sets the direction: what are we building, why, and what does "done" mean. One voice at the top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;King&lt;/strong&gt; routes the work: reads the contracts, decomposes the plan, fans out to workers, and validates results against the source of truth before anything ships.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workers&lt;/strong&gt; execute with fresh context windows — no accumulated drift, no stale assumptions from three tasks ago. Each worker gets exactly the slice it needs and nothing else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't hierarchy for its own sake — it's about giving each role the right context. Workers carrying full project state burn tokens and drift. A King doing the work loses the bird's-eye view. A God micromanaging workers becomes the bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch width matters.&lt;/strong&gt; Fan out as wide as the work genuinely decomposes — not wider. Serializing independent slices wastes time; padding the batch with invented work wastes compute. The King finds real seams, states cross-slice contracts up front, and lets workers run in parallel without mid-flight negotiation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anti-copy-paste note:&lt;/em&gt; your team might need two roles or five. The point isn't the names — it's that delegation has shape matching how your work decomposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Contracts as source of truth
&lt;/h2&gt;

&lt;p&gt;The fastest way to break an agent system is to let it guess. Agents extrapolate brilliantly and forget which extrapolation was wrong last time.&lt;/p&gt;

&lt;p&gt;Contracts fix this. Before any edit, the agent walks the contract chain for the file it's touching. The contract says what invariants hold, what seams exist, and what changed last time someone was here. After the edit, the contract updates. Gaps get filed and tracked.&lt;/p&gt;

&lt;p&gt;This isn't documentation. Documentation drifts. Contracts are &lt;strong&gt;blocking&lt;/strong&gt; — if a contract says "never add a subcommand that implies per-agent identity," the agent cannot add one. It's a live constraint, not a stale paragraph.&lt;/p&gt;

&lt;p&gt;The contract layer handles cross-cutting concerns too: deploy rules, testing gates, observability contracts, LLM routing rules. Agents that touch the system read the same source of truth.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anti-copy-paste note:&lt;/em&gt; the contract format here fits this project's size and velocity. A team of two might use a shared Markdown file. Fifty engineers, fifteen services — something heavier. The principle — "agents never guess what they can read" — is what travels.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Plan on the surface, route through contracts
&lt;/h2&gt;

&lt;p&gt;Agents are too eager to start coding. Give one a task and it'll open files before thinking about the shape of the change.&lt;/p&gt;

&lt;p&gt;The fix: &lt;strong&gt;planning must happen on a visible surface before execution.&lt;/strong&gt; The plan names what changes, what stays the same, what contracts it touches, and what the acceptance criteria are. The plan itself is a contract artifact — future agents (or future you) can read it to understand why something was built the way it was.&lt;/p&gt;

&lt;p&gt;Plans serve more than one audience: the human operator, the King decomposing work, Workers getting their slice, and the agent picking up this codebase months from now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anti-copy-paste note:&lt;/em&gt; the planning format here is property-based — "what must remain true after this change" — because that's what agents respect. Your team might plan differently. The principle is that planning happens &lt;em&gt;before&lt;/em&gt; action, on a surface everyone can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Drift detection as a first-class concern
&lt;/h2&gt;

&lt;p&gt;Systems that are never checked drift. Agent-orchestrated systems drift &lt;em&gt;faster&lt;/em&gt; — agents are confident and fast, a bad combination when the ground has shifted.&lt;/p&gt;

&lt;p&gt;Two checks help catch this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architecture drift scans&lt;/strong&gt; compare the live codebase against stated principles. If a principle says "every test creates a scratch workspace with guaranteed teardown" and a new test doesn't, the scan catches it. Gaps are either bugs or evidence that a new principle is needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract audits&lt;/strong&gt; walk the full contract chain for a path and flag anything that doesn't match. Cross-references, stale constraints, missing gap resolutions — surfaced before they cause surprises.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Anti-copy-paste note:&lt;/em&gt; pick a few invariants, check them automatically, treat every gap as a fix or a new invariant.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Every session ends with a retro
&lt;/h2&gt;

&lt;p&gt;Agents make mistakes. The question is whether they repeat them.&lt;/p&gt;

&lt;p&gt;After every significant session, the agent runs a retro: what went wrong, at what step, was it a model issue, a harness issue, or a task-scoping issue. Triaged by category, patterns become visible across sessions. A model that consistently fails at type-aware refactors gets flagged. A harness limitation blocking a whole class of tasks gets surfaced.&lt;/p&gt;

&lt;p&gt;The retro output can inform contracts and planning, so the next session's agent can reference the last mistakes before starting.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Urgent work has its own shape
&lt;/h2&gt;

&lt;p&gt;Urgent work doesn't fit the normal delegation pipeline. You don't want God→King→Worker fan-out when something needs fixing now.&lt;/p&gt;

&lt;p&gt;Urgent triage is a separate router: categorize the issue, scope its impact, run probes, route to the right responder. It trades planning depth for speed. The first job is containment — understanding can follow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anti-copy-paste note:&lt;/em&gt; What matters is that your normal workflow and your urgent workflow are &lt;em&gt;different things&lt;/em&gt; — don't try to make one pipeline handle both.&lt;/p&gt;




&lt;p&gt;None of this is magic. It's six patterns that emerged from watching agents fail in predictable ways and building guardrails at the failure points. Delegation needs shape. Contracts need to block, not just document. Plans need to be visible before action. Drift needs catching before it compounds. Sessions need to learn. Urgent work needs its own path.&lt;/p&gt;

&lt;p&gt;Take what makes your system safer. Ignore the rest. Your project is not my project — and that's the whole point.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Policy Cascades for Governed Multi-Tenant Agent Platforms</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Sat, 25 Jul 2026 06:36:27 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/policy-cascades-for-governed-multi-tenant-agent-platforms-436i</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/policy-cascades-for-governed-multi-tenant-agent-platforms-436i</guid>
      <description>&lt;p&gt;Most agent platforms give you one configuration file and hope. When you are&lt;br&gt;
running agents for more than one team — or more than one customer — a single&lt;br&gt;
config breaks down fast. Each workspace needs its own model, its own service&lt;br&gt;
access, its own secrets, but someone has to guarantee that no workspace can&lt;br&gt;
spend more than its budget or reach a service it was never authorized to use.&lt;/p&gt;

&lt;p&gt;The answer is a policy cascade. Every setting — model, temperature, allowed&lt;br&gt;
services, API keys, skill availability, TTL defaults — resolves through three&lt;br&gt;
ordered tiers: company, repo, and workspace. A lower tier can narrow an upstream&lt;br&gt;
ceiling but never widen it. That single rule is what makes it safe to hand a&lt;br&gt;
workspace to a team without handing them the keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the cascade resolves
&lt;/h2&gt;

&lt;p&gt;Three tiers, bottom-up. The company tier sets the floor. The repo tier overrides&lt;br&gt;
it. The workspace tier overrides both. The resolution order is fixed for every&lt;br&gt;
kind of setting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Company tier&lt;/th&gt;
&lt;th&gt;Repo tier&lt;/th&gt;
&lt;th&gt;Workspace tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Policy fields&lt;/td&gt;
&lt;td&gt;defaults&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variables&lt;/td&gt;
&lt;td&gt;floor&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;floor&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets&lt;/td&gt;
&lt;td&gt;last-resort fallback&lt;/td&gt;
&lt;td&gt;override&lt;/td&gt;
&lt;td&gt;first-resolved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that secrets run in reverse. A workspace-tier credential wins over the&lt;br&gt;
repo and company defaults, and an empty value at the workspace tier falls&lt;br&gt;
through to the repo. This means you set a per-customer token where it is used&lt;br&gt;
and fall back up the chain only when it is absent. You are never forced to&lt;br&gt;
duplicate credentials across every workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrow-only: the safety property
&lt;/h2&gt;

&lt;p&gt;The cascade is not a free-for-all override. For service access, budgets, quotas,&lt;br&gt;
and TTL defaults, a lower tier can only narrow what the tier above it allows. If&lt;br&gt;
the company grants a repo &lt;code&gt;[github, slack, search]&lt;/code&gt;, a workspace under that repo&lt;br&gt;
can select a subset — &lt;code&gt;[github, search]&lt;/code&gt; — but it cannot add &lt;code&gt;linkedin&lt;/code&gt;. The&lt;br&gt;
resolve-time intersection is enforced, not advisory.&lt;/p&gt;

&lt;p&gt;This extends to per-service API surface control. Granting access to GitHub does&lt;br&gt;
not mean granting access to every GitHub endpoint. A service entry can carry&lt;br&gt;
endpoint patterns — &lt;code&gt;/user/repos&lt;/code&gt; or &lt;code&gt;/repos/*&lt;/code&gt; — and any outbound call to an&lt;br&gt;
undeclared path is rejected before it leaves the platform. A lower tier can only&lt;br&gt;
further restrict those patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutability classes and hard locks
&lt;/h2&gt;

&lt;p&gt;Every policy field belongs to one of four mutability classes. Compliance fields&lt;br&gt;
(audit logging, token limits, prompt injection guards) are company-only — no&lt;br&gt;
lower tier can touch them. Identity fields (model, temperature, service access)&lt;br&gt;
cascade normally. Operational fields (budgets, quotas, memory settings) are open&lt;br&gt;
to any tier. Collection fields (per-workspace variables and secrets) are&lt;br&gt;
workspace-only and never pre-populated from above.&lt;/p&gt;

&lt;p&gt;On top of that, the company tier can place hard locks on individual fields. A&lt;br&gt;
locked field rejects every override from below — not silently ignored, but&lt;br&gt;
flatly rejected with a diagnostic. The lock is absolute until a company-tier&lt;br&gt;
operator clears it. We use this for compliance mandates: audit logging is&lt;br&gt;
company-wide, so it is hard-locked at the company tier and no workspace under&lt;br&gt;
that company can disable it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills form a floor, never a ceiling
&lt;/h2&gt;

&lt;p&gt;Company-tier skills are visible to every workspace under that company and cannot&lt;br&gt;
be removed. A workspace can add its own skills on top of the floor but it can&lt;br&gt;
never subtract from it. If a seed skill is deleted, the deletion is recorded as&lt;br&gt;
a durable tombstone rather than a physical removal — the floor is stable across&lt;br&gt;
restarts and redeploys. Workspace-authored skills that collide with a&lt;br&gt;
company-floor skill by ID are rejected at load time.&lt;/p&gt;

&lt;p&gt;Skills also obey the three-tier service ceiling: a skill that requires, say,&lt;br&gt;
GitHub access will not load for a workspace whose &lt;code&gt;allowed_services&lt;/code&gt; intersection&lt;br&gt;
does not include GitHub, regardless of whether the skill sits on the company&lt;br&gt;
floor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple companies, complete isolation
&lt;/h2&gt;

&lt;p&gt;A single policy file can define multiple companies, each with its own defaults,&lt;br&gt;
locks, skills, and secrets. The cascade never cross-contaminates between&lt;br&gt;
companies — a workspace under company A never inherits company B's runtime&lt;br&gt;
overrides, skills, or workflow catalog. Each company gets its own workspace&lt;br&gt;
singleton for live policy writes, and a company can opt into &lt;code&gt;hide_base_pack&lt;/code&gt; to&lt;br&gt;
suppress the platform's entire base skill and workflow catalog for its&lt;br&gt;
workspaces. This keeps a customer-facing instance clean — it sees only the&lt;br&gt;
skills and workflows authored for that customer, not the platform internals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two-layer storage with live effect
&lt;/h2&gt;

&lt;p&gt;Every tier stores policy in two layers. The git baseline is the cold-start seed&lt;br&gt;
— what you get on a fresh deploy. A runtime override layer, written to a&lt;br&gt;
persistent volume via CLI (&lt;code&gt;policy set&lt;/code&gt;, &lt;code&gt;vars set&lt;/code&gt;, &lt;code&gt;secret set&lt;/code&gt;), takes effect&lt;br&gt;
immediately. The resolver detects filesystem changes and re-resolves on next&lt;br&gt;
access — no restart, no redeploy. An operator can adjust a budget cap or&lt;br&gt;
add a service to a workspace and the change is live across the platform in the next&lt;br&gt;
request.&lt;/p&gt;

&lt;p&gt;The two layers can diverge intentionally — a workspace that starts from a&lt;br&gt;
company default of &lt;code&gt;temperature: 0.7&lt;/code&gt; and overrides it to &lt;code&gt;0.5&lt;/code&gt; keeps that&lt;br&gt;
override across restarts. The baseline remains the floor; promotion of a&lt;br&gt;
long-lived override back to the git baseline is available through a typed&lt;br&gt;
promotion verb when the operator decides it should become the new default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;The cascade model gives you a single answer to the question "what is this&lt;br&gt;
workspace's effective policy right now?" without hunting through files or&lt;br&gt;
guessing which override landed last. It gives you a safety perimeter — a&lt;br&gt;
workspace can never exceed the bounds its parent tiers set. And it gives you&lt;br&gt;
live mutability without a deploy cycle, because policy is data, not code.&lt;/p&gt;

&lt;p&gt;This is the governance model that runs the ToolShell agent platform.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>governance</category>
      <category>multitenancy</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your LLM Fallback Probably Isn't a Fallback</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Sat, 25 Jul 2026 06:20:22 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/your-llm-fallback-probably-isnt-a-fallback-34fk</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/your-llm-fallback-probably-isnt-a-fallback-34fk</guid>
      <description>&lt;p&gt;At 04:00 UTC, every model call through our LLM gateway started returning&lt;br&gt;
HTTP 400. Not some calls. All of them. Our tier-1 CI gate flagged it, and the&lt;br&gt;
fix was committed at 04:26 UTC the same morning — about 26 minutes end to end.&lt;/p&gt;

&lt;p&gt;This is the post-mortem.&lt;/p&gt;
&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;DeepSeek retired two API model names — &lt;code&gt;deepseek-chat&lt;/code&gt; and &lt;code&gt;deepseek-reasoner&lt;/code&gt; —&lt;br&gt;
at their V4 cutover around 2026-07-24 15:59 UTC. The replacements are&lt;br&gt;
&lt;code&gt;deepseek-v4-pro&lt;/code&gt; and &lt;code&gt;deepseek-v4-flash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our gateway config still declared both retired names. Starting roughly twelve&lt;br&gt;
hours after the retirement, every model request routed through the gateway hit a&lt;br&gt;
400 with the body:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but&lt;br&gt;
you passed .&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A live API check confirmed the shape of the cutover with four requests, same&lt;br&gt;
valid key:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model name&lt;/th&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepseek-v4-pro&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepseek-v4-flash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepseek-chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepseek-v4-pro-quantized&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two working names are the replacements. The two retired names — the ones our&lt;br&gt;
config referenced — returned 400. The fourth row is a name that does not exist&lt;br&gt;
at all, included because an earlier reading of a truncated error message had&lt;br&gt;
suggested it; shipping it would have left the platform broken. We'll come back&lt;br&gt;
to that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the fallback didn't help
&lt;/h2&gt;

&lt;p&gt;We had a fallback configured. Three separate model references in our policy&lt;br&gt;
config — the default CLI/workflow model, the chat model, and the shared fallback&lt;br&gt;
model — all pointed at the two retired names. All three lived under the same&lt;br&gt;
vendor and the same API key.&lt;/p&gt;

&lt;p&gt;When the primary call returned 400, the gateway tried the fallback. The log told&lt;br&gt;
the story in two adjacent lines: the 400 from the provider, and then&lt;br&gt;
&lt;code&gt;Error doing the fallback:&lt;/code&gt; carrying the identical error. The fallback died in&lt;br&gt;
the same instant as the primary because it was the same thing wearing a&lt;br&gt;
different label.&lt;/p&gt;

&lt;p&gt;This is the structural problem. A fallback that shares a provider and an API key&lt;br&gt;
with its primary is not resilience. It protects against a single model&lt;br&gt;
misbehaving. It protects against nothing at the provider level — an outage, a&lt;br&gt;
credential revocation, or a model rename all take down primary and fallback&lt;br&gt;
together. It also makes the "does the fallback work?" test structurally&lt;br&gt;
unpassable during any provider incident, because a red result cannot distinguish&lt;br&gt;
"fallback broken" from "provider down."&lt;/p&gt;
&lt;h2&gt;
  
  
  How we found it
&lt;/h2&gt;

&lt;p&gt;Our tier-1 CI test gate runs a minimal LLM reachability check on every push. On&lt;br&gt;
the failing run it reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;llm_reachability.py: FAIL: P2 'deepseek-reasoner' reachable
FAIL: P3 pi node executes end-to-end
FAIL: P4 chat succeeded despite dead primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gate's drift check flagged it as a regression — 108 of 109 checks passing,&lt;br&gt;
one new failure. The immediately preceding run of that same gate had been fully&lt;br&gt;
green, which proved the cause was external, not something we had just committed.&lt;/p&gt;

&lt;p&gt;A gate that catches a provider-side model retirement in a single CI run is the&lt;br&gt;
difference between finding out from a dashboard and finding out from a user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Two lines of gateway config: repoint the two aliases at &lt;code&gt;deepseek-v4-pro&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;deepseek-v4-flash&lt;/code&gt;. The alias names were left unchanged, so nothing downstream&lt;br&gt;
that referenced them had to change.&lt;/p&gt;

&lt;p&gt;The mapping was chosen by running the live API check above — not by reading the&lt;br&gt;
error string. An earlier reading of a truncated error message had suggested a&lt;br&gt;
model name (&lt;code&gt;deepseek-v4-pro-quantized&lt;/code&gt;) that does not exist. Shipping that&lt;br&gt;
would have left the platform broken. Four seconds of curl beat a confident&lt;br&gt;
guess.&lt;/p&gt;

&lt;p&gt;After the fix, both aliases returned HTTP 200 from inside the running pod, and&lt;br&gt;
the tier-1 gate returned to fully green with no drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we filed as the real defect
&lt;/h2&gt;

&lt;p&gt;The config fix closed the immediate outage. The real defect is the fallback&lt;br&gt;
design: same provider, same key, shared fate. We filed it as a tracked gap. It&lt;br&gt;
will stay open until the fallback crosses a provider boundary and carries&lt;br&gt;
independent credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your own stack — today
&lt;/h2&gt;

&lt;p&gt;Here is a concrete checklist you can run against your own setup right now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Does your fallback cross a provider boundary?&lt;/strong&gt; If primary and fallback&lt;br&gt;
both live under the same vendor and the same API key, they share a fate.&lt;br&gt;
A provider outage, credential revocation, or model deprecation will take&lt;br&gt;
both out simultaneously.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you have a reachability canary?&lt;/strong&gt; A single HTTP 200 check per declared&lt;br&gt;
model, running in CI on every push (or on a short cron), would have caught&lt;br&gt;
this in minutes. The check should be cheap — one token of output is enough&lt;br&gt;
to prove the model is alive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Would you find out from a gate or from a user?&lt;/strong&gt; If your answer is "a&lt;br&gt;
user," your monitoring has a coverage gap. The test that catches this does&lt;br&gt;
not need to be in production — a CI gate that fails on a model returning&lt;br&gt;
400 is sufficient and costs almost nothing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This incident came from a small platform still under active development. The&lt;br&gt;
lesson generalises to any system that routes LLM requests through a gateway.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>devops</category>
      <category>postmortem</category>
      <category>reliability</category>
    </item>
    <item>
      <title>Introducing the Agent Platform - 40% Complete (2026-04-28 23:21)</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Tue, 28 Apr 2026 20:21:08 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/introducing-the-agent-platform-40-complete-2026-04-28-2321-47ko</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/introducing-the-agent-platform-40-complete-2026-04-28-2321-47ko</guid>
      <description></description>
      <category>ai</category>
      <category>agents</category>
      <category>devops</category>
    </item>
    <item>
      <title>Meet the Agent Platform: A New Way to Work with Claude</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Tue, 28 Apr 2026 20:20:48 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/meet-the-agent-platform-a-new-way-to-work-with-claude-4j80</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/meet-the-agent-platform-a-new-way-to-work-with-claude-4j80</guid>
      <description>&lt;h1&gt;
  
  
  Meet the Agent Platform: A New Way to Work with Claude
&lt;/h1&gt;

&lt;p&gt;We're building something new: an &lt;strong&gt;Agent Platform&lt;/strong&gt; that lets workspaces bootstrap themselves from scratch.&lt;/p&gt;

&lt;p&gt;Imagine: an agent starts with nothing. A workspace, a certificate, and a walkthrough. The agent reads your instructions, creates a GitHub repo, publishes an article, integrates with Jira — whatever you need. Then it self-registers in policy and closes the bootstrap window.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P1&lt;/strong&gt;: Core dispatch routing, policy cascade (company → repo → workspace), lock enforcement, LiteLLM bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P2 (just shipped)&lt;/strong&gt;: Bootstrap cold-start — single-use certs, multi-provider support (GitHub, Dev.to), self-registration, one-way door pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's &lt;strong&gt;~40% complete&lt;/strong&gt;. We're moving fast.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P3&lt;/strong&gt;: Repositories API — let agents see what repos they own&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P4&lt;/strong&gt;: Capabilities expansion — more providers, richer self-registration flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P5&lt;/strong&gt;: Knowledge surface — agents building and querying shared knowledge&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Every SaaS today starts you with admin dashboards and manual setup. This platform says: &lt;strong&gt;agents set themselves up&lt;/strong&gt;. No admin panels. No human-in-the-loop config. Just cert → capability → done.&lt;/p&gt;




&lt;p&gt;Built by humans who believe AI should work smarter, not harder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/GadOfir/agent-platform-roadmap" rel="noopener noreferrer"&gt;https://github.com/GadOfir/agent-platform-roadmap&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Progress:&lt;/strong&gt; P1 ✓ P2 ✓ | Building P3-P5&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Made with:&lt;/strong&gt; Python, Docker, YAML, and a lot of coffee ☕&lt;/p&gt;

&lt;p&gt;Join us building the future of agentic workflows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>I'm writing this down before I lose the thread</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Wed, 22 Apr 2026 11:01:07 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/im-writing-this-down-before-i-lose-the-thread-245f</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/im-writing-this-down-before-i-lose-the-thread-245f</guid>
      <description>&lt;h1&gt;
  
  
  I'm writing this down before I lose the thread
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj5jfuq77ha1e2m6u0v3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj5jfuq77ha1e2m6u0v3.png" alt=" " width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been sketching an architecture for agent platforms for a while, and the pieces are starting to lock together. Time to get it out of my head.&lt;/p&gt;

&lt;p&gt;Two reasons for writing.&lt;/p&gt;

&lt;p&gt;First, selfish: good ideas rot in Slack DMs and half-finished notes. Writing forces the system to hold. If I can explain it, it's real.&lt;/p&gt;

&lt;p&gt;Second, more honest: a lot of this is about to be obvious. The LLM gateway/proxy space has exploded. Kong, LiteLLM, Portkey, Gravitee, Harness, everyone's building some version of the control plane. My specific combination feels new, but "new" has a shelf life measured in weeks right now. Flag planted.&lt;/p&gt;

&lt;p&gt;This is a build log, not a product announcement. I'm running a working version at workspace level with one agent end to end. Policy, router, RAG, tracing, CLI hub, all wired up. What's next is scaling the same pattern up to repo and company level. Some of what I say here is lived experience. Some is where I'm aiming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do agents actually live?
&lt;/h2&gt;

&lt;p&gt;This is the question that wouldn't let me go.&lt;/p&gt;

&lt;p&gt;Everyone builds agents. Almost nobody talks about where they live. You watch a demo, someone runs a command, the agent does a thing, the demo ends, the agent evaporates. Cool. Now do that for a hundred engineers across twenty repos for six months.&lt;/p&gt;

&lt;p&gt;The moment you try, you hit the questions nobody answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does the agent live when it's not actively running?&lt;/li&gt;
&lt;li&gt;How do you do long-horizon work, overnight or across days?&lt;/li&gt;
&lt;li&gt;Can you reconfigure it while it's running, or do you have to kill it?&lt;/li&gt;
&lt;li&gt;When it goes off the rails, who's watching, with what information?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody answers these because the answers commit you to a shape for the agent's world. Most frameworks punt. They give you the brain and hand you the body as an exercise. So I started designing the body.&lt;/p&gt;

&lt;p&gt;The shape I landed on: agents live in &lt;strong&gt;workspaces&lt;/strong&gt;. A workspace isn't a folder or a container or a session. It's a persistent, identity-bearing thing. It has a policy. It has inherited credentials. It attaches to repos. It has its own trace. When the agent stops, the workspace stays. When it resumes, everything is still there.&lt;/p&gt;

&lt;p&gt;And a workspace is also a place a human can walk into and take over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human-in-the-workshop, not human-in-the-loop
&lt;/h2&gt;

&lt;p&gt;Every enterprise AI deck has a slide that says "human in the loop" with a stick figure approving things. That framing is backwards, and it's why so many rollouts stall.&lt;/p&gt;

&lt;p&gt;Human-in-the-loop treats the human as a supervisor. An approval checkpoint. But the real thing is that humans and agents are doing the same job, and they need to do it in the same place, with the same tools, policies, and traces.&lt;/p&gt;

&lt;p&gt;If I design the workspace so a human can do everything the agent does (same CLI tools, same services, same tokens, same trace), then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debugging a broken run means stepping in and finishing the task&lt;/li&gt;
&lt;li&gt;Refining the agent means demonstrating correct behavior, which feeds back into config&lt;/li&gt;
&lt;li&gt;Traces don't distinguish human from agent, because the workspace identity is the unit that matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workspace stops being a cage and becomes a shared workbench. A human-AI workshop, not a supervision hierarchy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why no skills
&lt;/h2&gt;

&lt;p&gt;The industry has converged hard on "skills." Every framework ships with them. I'm not building them, and I want to say why, because it's the core of the bet.&lt;/p&gt;

&lt;p&gt;A skill, as the industry defines it, is a prompt the agent judges whether to invoke. You write a structured markdown file, the agent scans available skills, and decides on judgment whether it applies. When it picks right, it feels magical. When it doesn't, you're debugging vibes.&lt;/p&gt;

&lt;p&gt;Fine for exploratory work. Not fine for what I'm building.&lt;/p&gt;

&lt;p&gt;I'm building a deterministic system that AI drives. The AI will make mistakes. That's a given, not a bug. The job of the platform is to contain those mistakes inside flows that are solid and traceable. The flow doesn't get to happen or not happen based on the agent's mood. The flow is the thing.&lt;/p&gt;

&lt;p&gt;This is why the CLI Hub as a golden path matters so much. Every tool usage flows through a deterministic, traced, policy-enforced rail. I'm not hoping the agent picks the right skill. I'm constraining the ground it walks on. In my system the skill isn't a prompt. The skill &lt;em&gt;is&lt;/em&gt; the system. The CLI. The policy cascade. The workflow. The router.&lt;/p&gt;

&lt;p&gt;The industry's skill is a suggestion the agent considers. Mine is a rail the agent runs on.&lt;/p&gt;

&lt;p&gt;This isn't anti-AI. The more the platform handles determinism, the more the AI can be creative inside it. Ground the agent, then let it run. Don't ask it to ground itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracing is the floor, not the ceiling
&lt;/h2&gt;

&lt;p&gt;I used to think tracing was an observability concern. Something to add later, like metrics. I was wrong.&lt;/p&gt;

&lt;p&gt;Tracing is the foundation because agents generate trust deficits faster than logging can clear them. A human who made a weird commit, you ask them in standup. An agent that made a weird commit at 3am using an inherited Jira token to move a ticket, the only way anyone understands what happened is if the trace is good enough to reconstruct it. The policy that was resolved. The credential that was used. The workflow step. The repo. The model.&lt;/p&gt;

&lt;p&gt;Most gateway products trace the LLM call and stop. That's useless. The interesting question isn't "which model was called," it's "which workspace used which inherited credential at which step of which workflow against which repo." If your trace answers that sentence, you have governance. If it doesn't, you have a dashboard.&lt;/p&gt;

&lt;p&gt;Here's what a trace looks like in the system I'm running today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trace_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;7f9c2a...&lt;/span&gt;
  &lt;span class="s"&gt;workspace&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ws-backend-refactor-01&lt;/span&gt;
  &lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-opus-4.7&lt;/span&gt;
  &lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent&lt;/span&gt;          &lt;span class="c1"&gt;# or "human" when I step in&lt;/span&gt;
  &lt;span class="na"&gt;repo: inventory-service (policy layer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repo)&lt;/span&gt;
  &lt;span class="na"&gt;policy_source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model_allowlist&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;company.default&lt;/span&gt;
    &lt;span class="na"&gt;jira_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;company.default&lt;/span&gt;
    &lt;span class="na"&gt;rag_scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;          &lt;span class="s"&gt;repo.override        ← scoped to this repo&lt;/span&gt;
    &lt;span class="na"&gt;workflow_max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workspace.override&lt;/span&gt;

  &lt;span class="na"&gt;span&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.refactor_auth&lt;/span&gt;
    &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step.analyze_imports&lt;/span&gt;
      &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;router.resolve_policy    (2ms)&lt;/span&gt;
      &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="na"&gt;: rag.retrieve             (docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shared + repo, hit=4)  (180ms)&lt;/span&gt;
      &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="na"&gt;: llm.call                 (model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet-4.6, tokens&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.1k) (1.2s)&lt;/span&gt;
    &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step.apply_changes&lt;/span&gt;
      &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;router.resolve_policy    (1ms)&lt;/span&gt;
      &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tool.git_commit          (via cli-hub)                (80ms)&lt;/span&gt;
      &lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="na"&gt;: service.jira.update      (token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;company.default)     (220ms)&lt;/span&gt;
                                       &lt;span class="s"&gt;↑ this is attributable to ws-backend-refactor-01&lt;/span&gt;
                                         &lt;span class="s"&gt;even though the token is company-level&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail is the &lt;code&gt;policy_source&lt;/code&gt; block. For every value this workspace resolved, I can see which level it came from. Model allowlist: company default. Jira token: company default. RAG scope: overridden at the repo level. That's not a log line. That's a governance story.&lt;/p&gt;

&lt;p&gt;When I scale this to company level, that block gets more interesting. Right now every value resolves from company or workspace, because those are the two levels I've wired up. Once repos and workspace overrides cascade properly, this same trace starts showing "three of these values came from company, one was overridden by the repo, two were overridden by the workspace." Audit becomes a read, not a forensic archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG first, then lower
&lt;/h2&gt;

&lt;p&gt;Most LLM platforms are organized around "which model do we call." Wrong primitive.&lt;/p&gt;

&lt;p&gt;The primitive is "what knowledge does this request have access to." The model is a downstream detail.&lt;/p&gt;

&lt;p&gt;When a request comes in, the first question isn't GPT or Claude. It's: should this pull context from shared docs? From this repo's docs? From the workspace's scratch space? Should the response be re-grounded against docs on the way out? What's this agent allowed to know, and what are we obligated to cite?&lt;/p&gt;

&lt;p&gt;Answer those first, then lower the request into the model. If you build from the LLM up, RAG becomes a clumsy bolt-on. If you build from the knowledge boundaries down, RAG is the layer, and the LLM is just the execution engine.&lt;/p&gt;

&lt;p&gt;The router doesn't "route to models." It resolves a policy, applies RAG on input, picks a model, applies RAG on output, and dispatches to external services if needed. The LLM is one of several things that happen inside the resolved policy, not the center of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route, trace, RAG
&lt;/h2&gt;

&lt;p&gt;Compress it to a triangle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route&lt;/strong&gt;. Every request goes through one place. Right policy, right credentials, right model, right services. No bypass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace&lt;/strong&gt;. Everything emits structured, attributable signal. Workspace identity on every span. Per-workspace visibility into which level each config came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG&lt;/strong&gt;. Knowledge is a first-class layer, not a feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three verbs. Get them right and everything else is tractable. Get any one wrong and the system is insecure, opaque, or hallucinating. Pick your poison.&lt;/p&gt;

&lt;p&gt;Most platforms nail one. A few nail two. I haven't seen one that nails all three in a coherent way. That's the gap I'm building into.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea I keep not deleting: hivemind standup
&lt;/h2&gt;

&lt;p&gt;The speculative piece I can't shake.&lt;/p&gt;

&lt;p&gt;Imagine workspaces don't just sit idle between tasks. They wake up on their own cadence, call it a standup, and talk to each other about open issues. Each workspace has an energy budget proportional to how much meaningful work it did last cycle. High-signal work earns energy. Churn doesn't. They use that energy to flag blockers, propose collaborations, ask for help from workspaces with knowledge they don't have.&lt;/p&gt;

&lt;p&gt;The honest framing: this is a social test for the agents. Do they self-organize usefully, or just generate noise and cost? Do productive workspaces emerge as hubs? Does the hivemind surface blockers humans missed, or amplify them?&lt;/p&gt;

&lt;p&gt;I don't know. That's why I want to build it. A fleet of agents that can't cooperate without human orchestration is a fleet of expensive Mechanical Turks. A fleet that can is something else.&lt;/p&gt;




&lt;p&gt;Where I am right now: one workspace, full stack, running. Next is getting repo and company policy layers to cascade the way the design says they should. That's the proof point. The traces above are real in shape, simplified in detail. When repo and company are in, the &lt;code&gt;policy_source&lt;/code&gt; block starts telling a richer story, and I'll write that up too.&lt;/p&gt;

&lt;p&gt;If you're building something that rhymes with this, I want to talk. If you're shipping it already and I should just use yours, I also want to talk.&lt;/p&gt;

&lt;p&gt;Writing it down was step one.&lt;/p&gt;




</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>How I automated my own LinkedIn + Dev.to publishing in one afternoon (and what broke along the way)</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Wed, 15 Apr 2026 17:46:48 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/how-i-automated-my-own-linkedin-devto-publishing-in-one-afternoon-and-what-broke-along-the-way-21m</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/how-i-automated-my-own-linkedin-devto-publishing-in-one-afternoon-and-what-broke-along-the-way-21m</guid>
      <description>&lt;h1&gt;
  
  
  How I automated my own LinkedIn + Dev.to publishing in one afternoon (and what broke along the way)
&lt;/h1&gt;

&lt;p&gt;I'm a backend engineer transitioning to full-stack and trying to build more public presence. The plan: every time I ship something, a single command should write a proper article and post it to LinkedIn and a dev blog — so my GitHub activity actually becomes visible to recruiters without me having to remember.&lt;/p&gt;

&lt;p&gt;What I ended up building is a skill called &lt;code&gt;publish-project&lt;/code&gt; that sits inside my &lt;a href="https://github.com/GadOfir/LOS-starter" rel="noopener noreferrer"&gt;LOS&lt;/a&gt; memory system. You run one command, it reads a GitHub repo plus local project notes, writes a real article, and publishes it to Dev.to and LinkedIn in the same run.&lt;/p&gt;

&lt;p&gt;Here's the honest log of how it came together, including the three dead ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just "write a script"?
&lt;/h2&gt;

&lt;p&gt;I'm tired of writing scripts that generate LinkedIn posts with &lt;code&gt;[Add your motivation here...]&lt;/code&gt; placeholders I then fill in manually. The whole point was to eliminate that step. If I'm still hand-editing the output, automation bought me nothing.&lt;/p&gt;

&lt;p&gt;So the bar was: &lt;strong&gt;the article content has to come from real files on disk, not from a template the LLM fills in later.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead end 1: Medium
&lt;/h2&gt;

&lt;p&gt;I started with Medium because the &lt;a href="https://github.com/Medium/medium-api-docs" rel="noopener noreferrer"&gt;original Medium API docs&lt;/a&gt; describe a clean self-issued integration token flow. Looked straightforward.&lt;/p&gt;

&lt;p&gt;It isn't. Medium's docs now say, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT: We don't allow any new integrations with our API.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your account pre-dates the cutoff, your existing token still works. Mine didn't. There is no way to generate a new integration token for a new Medium account as of 2026. I checked the settings page — the Integration Tokens section literally isn't rendered for new accounts.&lt;/p&gt;

&lt;p&gt;Pivot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev.to: the easy path
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.forem.com/api" rel="noopener noreferrer"&gt;Dev.to's API&lt;/a&gt; is the opposite experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;https://dev.to/settings/extensions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generate an API key (one click)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/articles&lt;/code&gt; with your markdown and tags&lt;/li&gt;
&lt;li&gt;Done
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# max 4, lowercase, alphanumeric
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&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="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api/articles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole integration. Worked first try. I wrapped it in a &lt;code&gt;DevtoClient&lt;/code&gt; class with two methods (&lt;code&gt;get_user&lt;/code&gt;, &lt;code&gt;publish_article&lt;/code&gt;) and moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead end 2: LinkedIn's UGC Posts API
&lt;/h2&gt;

&lt;p&gt;LinkedIn was supposed to be the easy one. I followed an old tutorial, built the request, and got this back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error 422: com.linkedin.common.error.BadRequest
"com.linkedin.ugc.UGCContent" is not a member type of union [...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some digging I found the cause: &lt;strong&gt;LinkedIn deprecated the &lt;code&gt;/v2/ugcPosts&lt;/code&gt; endpoint in favour of a new &lt;code&gt;/rest/posts&lt;/code&gt; endpoint.&lt;/strong&gt; The old payload shape (&lt;code&gt;specificContent.com.linkedin.ugc.UGCContent.shareCommentary.text&lt;/code&gt;) is gone. The new one is dramatically simpler:&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="n"&gt;POST&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linkedin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;

&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bearer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Restli&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;Linkedin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;202604&lt;/span&gt;
  &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urn:li:person:&amp;lt;id&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commentary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your post text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUBLIC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distribution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feedDistribution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAIN_FEED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetEntities&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thirdPartyDistributionChannels&lt;/span&gt;&lt;span class="sh"&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lifecycleState&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUBLISHED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;isReshareDisabledByAuthor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things that tripped me up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The endpoint is &lt;code&gt;https://api.linkedin.com/rest/posts&lt;/code&gt;, &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;https://api.linkedin.com/v2/rest/posts&lt;/code&gt;. I had &lt;code&gt;LINKEDIN_API = "https://api.linkedin.com/v2"&lt;/code&gt; as a constant and was string-concatenating &lt;code&gt;/rest/posts&lt;/code&gt; onto it, which produced a 404 &lt;code&gt;RESOURCE_NOT_FOUND&lt;/code&gt;. I split the constant into &lt;code&gt;LINKEDIN_API_V2&lt;/code&gt; (for &lt;code&gt;/v2/userinfo&lt;/code&gt;) and &lt;code&gt;LINKEDIN_API&lt;/code&gt; (bare base URL) to fix this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A successful &lt;code&gt;POST /rest/posts&lt;/code&gt; returns &lt;strong&gt;201 with an empty body&lt;/strong&gt;. The post ID comes back in the &lt;code&gt;x-restli-id&lt;/code&gt; response header. My client initially tried to &lt;code&gt;.json()&lt;/code&gt; the empty body and crashed with "Expecting value: line 1 column 1". Fixed with a &lt;code&gt;try/except&lt;/code&gt; that falls back to reading the header.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LinkedIn also has content-hash deduplication. If you POST the exact same commentary twice within a short window, the second call fails with &lt;code&gt;DUPLICATE_POST&lt;/code&gt;. This is actually a gift — it saved me from spamming my own feed while testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead end 3: generating text that looked real but wasn't
&lt;/h2&gt;

&lt;p&gt;This is where I spent the most time and made the most mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 1:&lt;/strong&gt; I wrote a template function that produced markdown with placeholders: "Here's what I learned: [Add 3-5 takeaways]". It "worked" in that the script ran. But the published article was garbage because I forgot that &lt;em&gt;nobody was going to fill those placeholders in&lt;/em&gt;. I published it to Dev.to before I read what it looked like. I had to delete it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 2:&lt;/strong&gt; I hardcoded all the text directly into the Python script — a long f-string with generic "the task system was the whole point" narration. Better than placeholders, but it was the same article regardless of which repo I pointed it at. Not reusable. Still technically wrong in places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 3:&lt;/strong&gt; I rewrote the whole generator to read real files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET https://api.github.com/repos/{owner}/{repo}&lt;/code&gt; for metadata&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /repos/{owner}/{repo}/readme&lt;/code&gt; (with &lt;code&gt;Accept: application/vnd.github.v3.raw&lt;/code&gt;) for full README content&lt;/li&gt;
&lt;li&gt;Local filesystem reads from &lt;code&gt;memory/projects/&amp;lt;project&amp;gt;/tasks.md&lt;/code&gt; and &lt;code&gt;decisions.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A small markdown table parser that extracts rows into dicts, keyed by column header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The table parser turned out to be the crucial piece. My &lt;code&gt;tasks.md&lt;/code&gt; and &lt;code&gt;decisions.md&lt;/code&gt; follow a consistent shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Task | Why | Priority | Status |
|------|-----|----------|--------|
| Adopt BMAD patterns (task-001) | Onboarding gate, state tracking | 1 | Done |
| Cross-repo sync (task-002) | Both repos need identical task-system | 1 | Done |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser walks the lines, finds a header row matching a required set of columns, and yields each data row as &lt;code&gt;{"task": ..., "why": ..., "status": ...}&lt;/code&gt;. The generator then interpolates real task names and real decisions into the article body — no placeholders, no hardcoded claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three mistakes I made after that
&lt;/h2&gt;

&lt;p&gt;Even with real data, I still shipped bad articles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I claimed "5 tasks built it"&lt;/strong&gt; based on the fact that &lt;code&gt;los-starter/tasks.md&lt;/code&gt; has 5 rows. But those 5 are just the infrastructure-bootstrap tasks for one subfolder — the actual LOS project is many skills, a landing page, a memory system, an update mechanism. The number was technically correct but the framing was dishonest. Cut it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I called LOS a "file-based OS for my dev work"&lt;/strong&gt;. It isn't. LOS is a &lt;em&gt;memory system&lt;/em&gt; for Claude Code — markdown files that Claude reads at session start so every conversation picks up where the last one left off. Calling it an "OS" flattened the core idea.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I included 8 skills&lt;/strong&gt; in the skills list. The LOS-starter README says 6. I had added &lt;code&gt;/update-los&lt;/code&gt; and &lt;code&gt;/publish-project&lt;/code&gt; to my "core skills" set because they exist on disk, but they aren't in the public skill lineup. Trimmed to 6.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these was a two-line fix. Each one was also the kind of subtle wrong you can only spot by reading the output carefully, not by running tests. If you're automating publishing, &lt;strong&gt;read every post before it goes live, even in &lt;code&gt;--confirm&lt;/code&gt; mode.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the skill does now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Preview everything without posting&lt;/span&gt;
python publish_project.py &lt;span class="nt"&gt;--repo&lt;/span&gt; LOS-starter

&lt;span class="c"&gt;# Publish article to Dev.to + post to LinkedIn&lt;/span&gt;
python publish_project.py &lt;span class="nt"&gt;--repo&lt;/span&gt; LOS-starter &lt;span class="nt"&gt;--confirm&lt;/span&gt;

&lt;span class="c"&gt;# Post a fresh LinkedIn post pointing at an existing Dev.to article&lt;/span&gt;
python publish_project.py &lt;span class="nt"&gt;--repo&lt;/span&gt; LOS-starter &lt;span class="nt"&gt;--linkedin-only&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--article-url&lt;/span&gt; https://dev.to/you/your-article &lt;span class="nt"&gt;--confirm&lt;/span&gt;

&lt;span class="c"&gt;# Just write the article to a local markdown file for review&lt;/span&gt;
python publish_project.py &lt;span class="nt"&gt;--repo&lt;/span&gt; LOS-starter &lt;span class="nt"&gt;--save&lt;/span&gt; out.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fetch GitHub&lt;/strong&gt; — repo metadata and full README from the GitHub API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read local project&lt;/strong&gt; — look for &lt;code&gt;memory/projects/&amp;lt;name&amp;gt;/&lt;/code&gt;, parse &lt;code&gt;tasks.md&lt;/code&gt; and &lt;code&gt;decisions.md&lt;/code&gt; as markdown tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read core skills&lt;/strong&gt; — scan &lt;code&gt;.claude/skills/*/SKILL.md&lt;/code&gt;, extract description from frontmatter, filter to the skills actually listed in my public README&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick the richer README&lt;/strong&gt; — compare section counts between GitHub README and local README, use whichever has more structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate article&lt;/strong&gt; — interpolate all of the above into an article body with real sections: idea, stack, features, skills, one skill deep-dive, real tasks table, real decisions list, architecture notes, try-it block&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build LinkedIn post&lt;/strong&gt; — short version with both the Dev.to URL and the GitHub URL on their own lines (LinkedIn renders link previews only when a URL is on its own line)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; — Dev.to first (because we need the URL for the LinkedIn post), then LinkedIn with the fresh URL included&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Three inputs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A GitHub repo name&lt;/strong&gt; via &lt;code&gt;--repo &amp;lt;name&amp;gt;&lt;/code&gt;. Bare repo names are resolved against my GitHub username; full &lt;code&gt;owner/repo&lt;/code&gt; works too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A local project folder at &lt;code&gt;memory/projects/&amp;lt;slug&amp;gt;/&lt;/code&gt;&lt;/strong&gt; (optional but strongly recommended). This is where the interesting data lives: &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;tasks.md&lt;/code&gt;, &lt;code&gt;decisions.md&lt;/code&gt;. Without this, the article is just README paraphrase. With it, the article has specific decisions with dates and real task names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt; with three tokens&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;   &lt;span class="py"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ghp_...&lt;/span&gt;
   &lt;span class="py"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
   &lt;span class="py"&gt;LINKEDIN_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub token is optional (raises the rate limit from 60/hr unauthenticated to 5000/hr). The other two are required if you want to actually publish.&lt;/p&gt;

&lt;p&gt;That's the whole contract. If you keep your project notes in the LOS format — one folder per project with a README, a tasks table, and a decisions table — the skill has everything it needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Two things I'd fix if I were building this again:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotent publishing.&lt;/strong&gt; The current script calls &lt;code&gt;POST /api/articles&lt;/code&gt; on every &lt;code&gt;--confirm&lt;/code&gt; run, which means every iteration creates a &lt;em&gt;new&lt;/em&gt; Dev.to article. I generated four stale drafts before I realised this. The right design: maintain a local &lt;code&gt;.published.json&lt;/code&gt; state file mapping &lt;code&gt;repo_name → article_id&lt;/code&gt;, and on subsequent runs hit &lt;code&gt;PUT /api/articles/{id}&lt;/code&gt; to update the existing article in place. Dev.to supports it; I just didn't wire it up. Next iteration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preview the final LinkedIn text.&lt;/strong&gt; My preview mode showed the LinkedIn post &lt;em&gt;before&lt;/em&gt; the Dev.to URL was known, so the preview was missing the "Full write-up on Dev.to:" line even though the published version had it. That's a confusing UX — the preview didn't match the output. I patched it to use a placeholder URL in the preview and rebuild the post with the real URL at publish time, but the cleaner fix is to publish to Dev.to first (always), then show the final LinkedIn text, then prompt for confirmation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;I am trying to go from "person with a decent GitHub" to "person recruiters find". The difference isn't the code — it's whether anyone sees it. A project that ships with an accompanying write-up every time will, over a year, build far more public signal than a project that ships in silence.&lt;/p&gt;

&lt;p&gt;This skill is not glamorous. It's a markdown table parser, two HTTP clients, and one generator function. It cost me an afternoon plus three hours of debugging the old LinkedIn API. But it means my next project ships with a real article and a real LinkedIn post, and the one after that, and the one after that — without me having to remember to write them.&lt;/p&gt;

&lt;p&gt;Full LOS repo: &lt;a href="https://github.com/GadOfir/LOS-starter" rel="noopener noreferrer"&gt;https://github.com/GadOfir/LOS-starter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More on YouTube: &lt;a href="https://www.youtube.com/@GadOfir" rel="noopener noreferrer"&gt;https://www.youtube.com/@GadOfir&lt;/a&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>claudecode</category>
      <category>linkedin</category>
      <category>devtools</category>
    </item>
    <item>
      <title>LOS-starter: The Markdown Memory System That Makes Claude Code Remember</title>
      <dc:creator>Gad Ofir</dc:creator>
      <pubDate>Wed, 15 Apr 2026 17:38:17 +0000</pubDate>
      <link>https://dev.to/gad_ofir_076c468dd15d483b/los-starter-the-markdown-memory-system-that-makes-claude-code-remember-1k7</link>
      <guid>https://dev.to/gad_ofir_076c468dd15d483b/los-starter-the-markdown-memory-system-that-makes-claude-code-remember-1k7</guid>
      <description>&lt;h1&gt;
  
  
  LOS-starter: The Markdown Memory System That Makes Claude Code Remember
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Public cold-start template for the Life Operating System. Users clone it, run onboarding, and get a working LOS instance with task-system, skills, and memory. Also the upstream source for &lt;code&gt;/update-los&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it exists:&lt;/strong&gt; Gives people a single place to understand LOS and bootstrap their own instance. Managed files are pulled from this repo during updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/GadOfir/LOS-starter" rel="noopener noreferrer"&gt;https://github.com/GadOfir/LOS-starter&lt;/a&gt;  &lt;strong&gt;Version:&lt;/strong&gt; v0.3.0  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;Static HTML landing page (GitHub Pages) + Claude Code skills + markdown-based system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Task system (BMAD-inspired plan phase, loop mode, cross-repo support)&lt;/li&gt;
&lt;li&gt;Self-assembling routing (skills declare their own triggers via SKILL.md)&lt;/li&gt;
&lt;li&gt;Update mechanism (&lt;code&gt;/update-los&lt;/code&gt; — migration + regular updates)&lt;/li&gt;
&lt;li&gt;Identity in &lt;code&gt;memory/identity.md&lt;/code&gt; (survives updates)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;LOS:managed&lt;/code&gt; markers on all updatable files&lt;/p&gt;
&lt;h2&gt;
  
  
  The skills that make it work
&lt;/h2&gt;

&lt;p&gt;Every workflow in LOS is a Claude Code skill. Here's what's actually in &lt;code&gt;.claude/skills/&lt;/code&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/learn&lt;/code&gt;&lt;/strong&gt; — Captures and structures knowledge into memory/knowledge/. Handles two modes: topic learning and source ingestion&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/build-project&lt;/code&gt;&lt;/strong&gt; — Creates a new project in memory/projects/ with standard structure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/task-system&lt;/code&gt;&lt;/strong&gt; — Manages a single-task-at-a-time project workflow inside the repo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/evolve&lt;/code&gt;&lt;/strong&gt; — Runs a full LOS system review, health check, and improvement suggestions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/create-skill&lt;/code&gt;&lt;/strong&gt; — Creates new Claude Code skills for LOS following Skills 2.0 conventions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/design-html&lt;/code&gt;&lt;/strong&gt; — Creates beautiful single-file HTML pages with modern CSS. No frameworks, no build tools&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skills self-register by dropping a &lt;code&gt;SKILL.md&lt;/code&gt; file into &lt;code&gt;.claude/skills/{name}/&lt;/code&gt;. No central registry. No hardcoded lists. The routing layer reads the frontmatter and dispatches automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory is the whole point
&lt;/h2&gt;

&lt;p&gt;I didn't want another Notion database. I wanted Claude to remember what I'm working on — across conversations, across compactions, across weeks.&lt;/p&gt;

&lt;p&gt;LOS does that by storing everything in plain markdown files under &lt;code&gt;memory/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memory/
├── identity.md         # who I am, current state, active task
├── projects/           # one folder per active project
│   └── my-project/
│       ├── README.md
│       ├── tasks.md
│       └── decisions.md
└── knowledge/          # things I've learned, organised by topic
    ├── docker.md
    └── react.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At session start, Claude reads &lt;code&gt;memory/identity.md&lt;/code&gt; and knows: who I am, what projects are active, and what I was working on last. No onboarding, no re-explaining context.&lt;/p&gt;

&lt;p&gt;Updates to LOS replace managed files cleanly — your memory never gets touched because &lt;code&gt;identity.md&lt;/code&gt; is outside the managed scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  One skill deep-dive: /task-system
&lt;/h2&gt;

&lt;p&gt;Of the six skills, &lt;code&gt;/task-system&lt;/code&gt; is the one with the most structure. It runs a BMAD-inspired lifecycle on a single task at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plan → build → verify → fix → learn → close
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan phase wears three thinking hats in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Analyst&lt;/strong&gt; — what are we really solving, what's the domain context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architect&lt;/strong&gt; — what's the technical approach, what could break&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PM&lt;/strong&gt; — break it into 8-10 concrete steps, each one atomic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three hats write into &lt;strong&gt;one file&lt;/strong&gt; — &lt;code&gt;.tasks/active/task-NNN.md&lt;/code&gt;. Analysis, architecture notes, plan, work log, verify results, fix attempts, learnings — one file is the truth. No scattered briefs, no Google Docs, no side files.&lt;/p&gt;

&lt;p&gt;Two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gated&lt;/strong&gt; — human approves each phase. Default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop&lt;/strong&gt; — Claude auto-advances through phases until STUCK, BLOCKED, or PASS. Use this when you're away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Status vocabulary is deliberately small: &lt;code&gt;IN_PROGRESS&lt;/code&gt;, &lt;code&gt;FAIL&lt;/code&gt;, &lt;code&gt;STUCK&lt;/code&gt;, &lt;code&gt;BLOCKED&lt;/code&gt;, &lt;code&gt;PASS&lt;/code&gt;, &lt;code&gt;ABANDONED&lt;/code&gt;. STUCK is the only full-stop — two failed fix attempts and the loop exits, waiting for a human.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real tasks that built this project
&lt;/h2&gt;

&lt;p&gt;This isn't theoretical. Here are the actual tasks from &lt;code&gt;memory/projects/LOS-starter/tasks.md&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Adopt BMAD patterns (task-001)&lt;/td&gt;
&lt;td&gt;Onboarding gate, state tracking, enriched skills&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-repo sync (task-002)&lt;/td&gt;
&lt;td&gt;Both repos need identical task-system structure&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autonomy + handoff guards (task-003)&lt;/td&gt;
&lt;td&gt;Loop mode pausing, cross-repo build in wrong repo&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update mechanism (task-004)&lt;/td&gt;
&lt;td&gt;No safe way to update old LOS instances&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-assembling routing (task-005)&lt;/td&gt;
&lt;td&gt;Updates wiped custom skill entries from CLAUDE.md&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each of these was a single task file, planned with three hats, built, verified, and closed. The fact that they're all &lt;code&gt;Done&lt;/code&gt; is the task system working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decisions I had to make
&lt;/h2&gt;

&lt;p&gt;Every decision is logged in &lt;code&gt;decisions.md&lt;/code&gt; with a reason. This file answers the question &lt;em&gt;"why is it done this way?"&lt;/em&gt; without anyone having to read the code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Host on GitHub Pages as static HTML&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;2026-03-28&lt;/em&gt; — Zero-cost, zero-ops, fast to iterate&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single landing page (no framework)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;2026-03-28&lt;/em&gt; — Keeps it simple, matches LOS philosophy&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity in memory/identity.md, not CLAUDE.md&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;2026-03-29&lt;/em&gt; — Makes CLAUDE.md 100% replaceable on update&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LOS:managed markers for update boundary&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;2026-03-29&lt;/em&gt; — Any file with marker gets replaced, everything else untouched&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-assembling routing from skill frontmatter&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;2026-03-29&lt;/em&gt; — No hardcoded skill lists — custom skills auto-route&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No CLI for updates — Claude is the update tool&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;2026-03-29&lt;/em&gt; — /update-los skill fetches from GitHub, simpler than npm tooling&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;p&gt;Most dev tooling fights Claude Code. LOS works with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory is the foundation.&lt;/strong&gt; &lt;code&gt;memory/identity.md&lt;/code&gt; is the source of truth for who I am and what's active. Claude reads it at session start — no re-onboarding, ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity survives updates.&lt;/strong&gt; &lt;code&gt;/update-los&lt;/code&gt; can replace CLAUDE.md, routing, and skills cleanly because your identity lives outside the managed scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills are self-assembling.&lt;/strong&gt; Drop a new skill into &lt;code&gt;.claude/skills/my-thing/SKILL.md&lt;/code&gt;, and the routing layer finds it via frontmatter. No config to edit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LOS:managed&lt;/code&gt; markers&lt;/strong&gt; — every updatable file is tagged so the update mechanism knows exactly what it's allowed to touch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One file per task.&lt;/strong&gt; The &lt;code&gt;/task-system&lt;/code&gt; skill enforces a single active task file. No scatter, no lost context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try 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/GadOfir/LOS-starter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;LOS-starter
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then say &lt;code&gt;"start session"&lt;/code&gt; and let it bootstrap your first task.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/GadOfir/LOS-starter" rel="noopener noreferrer"&gt;https://github.com/GadOfir/LOS-starter&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Built with:&lt;/strong&gt; Claude Code + markdown + zero frameworks&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>opensource</category>
      <category>tooling</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
