<?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.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>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>
