A weekly operating loop for someone running a portfolio of work: an LLM drafts the review from your private notes, you approve one diff, it keeps the record and tells your shared tracker what it needs to know.
flowchart LR
A["Notes: cards, daily logs,<br/>task digest, meeting notes"] --> B["Draft:<br/>week-open or week-close"]
B --> C["Walkthrough:<br/>section by section, pause for edits"]
C --> D["One consolidated diff"]
D --> E{"Approve?"}
E -- "yes, full or partial" --> F["Atomic apply<br/>tools/kanban.py"]
F --> G["Lock the weekly file"]
G --> H["Derive initiative rows"]
H --> I["Push<br/>sync/sync.py"]
I --> J[("Your PMO tool")]
E -- "no, more edits" --> C
If you run more than about five things at once, the true state of your work lives in three places at once: your head, a pile of scattered notes, and a shared tracker that's always a week behind because updating it properly feels like a second job. Most "productivity" tooling solves this by making you type more, into more places.
weekloop tries the opposite. You keep writing your notes the way you already do, in your own private, full-fidelity format: kanban cards, daily logs, meeting notes, whatever you've got. Twice a week, an agent reads all of it, drafts a review, and walks you through it section by section. At the end it doesn't propose forty small edits, it proposes ONE consolidated diff to your board: which cards move, which get a due date, which get a tag fix, which are brand new. You approve all of it, part of it, or none of it. What you approve gets applied in one atomic pass, the weekly file locks with a timestamp, and only then does a small, deliberately thin slice of it (initiative name, health, one-line update, who owns the next step) get derived and pushed to whatever tool the rest of your organisation actually reads. Everything else, the personal tasks, the candid notes, the half-formed thoughts, stays where you wrote it.
The compounding bit is the split: author once, privately and completely, and derive the shared layer from that instead of maintaining both by hand. The private layer never gets thinner because updating the shared one is cheap. The shared layer never drifts because it's generated from the same walkthrough you already did for yourself.
This is an idea file plus a working reference implementation. It is not a product. There's no server, no account, no background scheduler baked in. It's a Claude Code skill (or a copy-paste prompt for any other LLM chat), two small Python scripts, and a file format. I built it to run my own week and I'm not going to support it as a product.
- weekloop finds this week's draft open file, or generates one from the template.
- It reads every file the draft references before saying anything to you: cards, daily logs, the current task digest. No claim gets made without a file behind it.
- It runs a read-only hygiene pass over the board (
kanban-hygiene): stale cards, missing due dates, malformed tags, orphan cards. - It walks you through top priorities, cards landing this week, risks and blockers, and standing items, one section at a time, pausing for your edits at each one.
- It proposes one diff, in a fixed order: status moves, then due-date changes, then tag fixes, then new cards. Hygiene findings with a mechanical fix get folded in as diff lines instead of raised separately.
- You approve all, part, or none of it.
tools/kanban.py applywrites every approved change in one pass, card frontmatter and the board file together, or writes nothing at all if anything about the diff no longer matches the board.tools/kanban.py lockstamps the weekly filestatus: lockedwith a timestamp.- You approve a short table of initiative rows (Plan, one per initiative actually
moving this week), and
sync/sync.pypushes it to your shared tool.
- Same shape, close flavour: weekloop rolls up the week's sessions, decisions made, cards moved, due/overdue items, and open threads.
- Same hygiene pass, same one-diff proposal, same approve-then-atomic-apply, same lock.
- The initiative rows this time are Actual, not Plan: what actually happened, health set honestly rather than optimistically.
- If your organisation has a downstream leadership update that reuses the week as its spine, this locked file is the thing to point it at.
Prerequisites: Python 3.10+, pip.
git clone https://github.com/Jiplet/weekloop.git
cd weekloop
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Try it against the bundled synthetic example first, before touching your own board.
pytest -q
# 23 passed
python3 tools/kanban.py apply ops-example/diffs/2026-W33-open.diff.md --root ops-example --dry-run --as-of 2026-08-10
# prints the plan: 3 status moves, 1 due-date change, 1 tag fix, 1 new card
python3 sync/sync.py ops-example/weekly/2026-08/2026-W32-close.md --config weekloop.config.example.yaml --dry-run
# prints the 3 derived Actual rows as a tableOnce that all runs clean, point it at your own board: cp weekloop.config.example.yaml weekloop.config.yaml (gitignored) and fill in your initiatives, sectors, and sink.
On Claude Code: copy .claude/skills/weekloop/ and .claude/skills/kanban-hygiene/
into your own vault's .claude/skills/, point the paths in this README's templates at
your own kanban/ and weekly/ folders, and type /weekloop.
Without Claude Code, or on any other LLM: open prompts/weekloop-open.md (or
weekloop-close.md), paste the whole thing into a chat along with your board and
notes where marked, and follow along. You apply the resulting diff yourself with
tools/kanban.py, since a plain chat can't touch your files.
| Path | What it is | When you touch it |
|---|---|---|
.claude/skills/weekloop/ |
The Claude Code skill: locates or drafts the weekly file, walks the review, builds the diff, applies and locks, derives rows | Copy into your own vault, adapt paths |
.claude/skills/kanban-hygiene/ |
Read-only hygiene pass, the six checks | Copy alongside weekloop, or run standalone |
prompts/*.md |
Plain-LLM equivalents of the two skills, for any chat interface | Paste-and-fill, no setup |
templates/*.md |
Blank weekly files, card, board, and task-digest templates | Copy when scaffolding a fresh board |
ops-example/ |
A complete synthetic week: 6 cards, a board, an open draft, a locked close file, two daily logs, a task digest, an approved diff | Read it to see the shape, or run the commands above against it |
tools/kanban.py |
Parses a diff file, validates it against the board on disk, applies atomically, locks a weekly file | Run it, or read it before extending the diff format |
sync/sync.py |
Reads a locked weekly file's ## Initiative rows table, computes week-ending, pushes via a connector |
Run it, point --config at your own weekloop.config.yaml |
sync/connectors/csv_sink.py |
Append-only CSV connector | Default sink, zero setup |
sync/connectors/json_sink.py |
Append-only JSON connector | For piping into something else |
sync/connectors/smartsheet.py |
Reference connector: column-by-name, stdlib only, documented gotchas | Adapt for your own Smartsheet sheet, or as a model for a different tool |
weekloop.config.example.yaml |
Initiatives, sectors, sink choice, connector settings | Copy to weekloop.config.yaml (gitignored) and fill in your own |
.env.example |
Secret template | Copy to .env, only needed for the Smartsheet sink |
tests/ |
pytest suite, all against tmp copies of ops-example/ |
Run before you trust a change to tools/kanban.py or sync/sync.py |
Makefile |
test, dry-run-open, dry-run-close, apply, lock, sync-dry |
Discoverability, all wrap the commands above |
Your kanban schema. Card frontmatter, status values, and sector tags are all
defined by convention, not by code: tools/kanban.py's COLUMN_ORDER list and the
diff regexes are the only places that assume the five-column shape (Backlog,
This week, In Progress, Review, Done). Change the list, update
templates/master.md, and the rest follows. weekloop.config.yaml's initiatives
and sectors lists scope what sync.py is willing to push.
Your PMO tool. Write a new file under sync/connectors/ with two functions,
push_rows(rows, config) and read_rows(config), matching the shape in
csv_sink.py. Point sink: in your config at the new module name. sync/sync.py
does not need to change.
Another agent. The prompts/ versions are deliberately model-agnostic: they
assume nothing except that you can paste text in and copy text out. If you're
building this into a different agent framework, the fixed diff format (see any
prompts/*.md file) is the only contract that matters: as long as your agent can
produce those four sections in that order, tools/kanban.py doesn't care what wrote
them.
Why one diff, not many. An agent that asks you to approve forty small edits across a session trains you to rubber-stamp, which defeats the point of having a human in the loop at all. One consolidated diff, in a fixed section order, is small enough to actually read and gives you one moment where you're really deciding, not forty moments where you're really just clicking yes.
Why apply is atomic. tools/kanban.py apply validates every line of the diff
against the board on disk (does the card exist, does its current status match what the
diff claims) before writing a single file. If anything's changed since the diff was
drafted, the whole apply aborts with no partial writes, rather than leaving three cards
updated and a fourth silently skipped. A half-applied diff is worse than no diff:
it's a plausible-looking board that's quietly wrong.
Why the orphan fix reuses the status-move code path. A card that exists in
cards/ but never made it onto master.md needs the same operation as an ordinary
status move (put this card in this column), it just happens to have the same old and
new status. Rather than a separate "add to board" mechanism, a status move where
old equals new is a no-op on the card's frontmatter but still walks the board and
makes sure the card appears exactly once, in that column. One code path, one thing to
trust.
Why lock. A weekly file that's still marked draft after you've already acted on
it invites drift: someone reopens it, edits a section, and now the record doesn't
match what actually happened. Locking is a deliberate, cheap "this is now history"
marker. tools/kanban.py lock refuses to re-lock an already-locked file for the same
reason.
Why initiative-grain only leaves the private layer. The shared tool is read by people who don't need, and shouldn't see, your personal task list or a half-formed thought from Tuesday's stand-up. Scoping the derived table to "one row per initiative that actually moved" is both a privacy boundary and a signal-to-noise decision: a shared tracker that gets a bulk dump every week stops getting read.
The draft-lane idea, generalised. A pattern worth stealing even if you don't build the rest of this: let automation draft cheaply into a place nothing downstream trusts yet (an inbox lane, or here, a diff nobody has approved), let a human promote what's good with one deliberate action, and never let the automation write directly to anything live. weekloop applies this to the whole week's changes at once rather than to individual new cards, but it's the same trust boundary: propose, approve, commit.
MFA and API asymmetries are real, plan for them. The Smartsheet connector's
docstring documents a specific, non-obvious trap: some tenants block GET on a raw
API token under an MFA policy while POST on the same resource works fine. A naive
"push then verify" script will report a failure that isn't one. If you're wiring up
any enterprise API, don't assume read and write permissions are symmetric just because
they usually are.
- No scheduler is included. Nothing here decides it's Monday for you: wire up your own cron, launchd, or "run this when I say so" trigger.
- Single board owner, no conflict resolution. This assumes one person approving one diff at a time, not concurrent editors.
- The Smartsheet connector is a reference implementation: no pagination, no retry, no rate-limit backoff. It works for the "a handful of rows, once or twice a week" volume this is built for, not for a bulk migration.
tools/kanban.py's frontmatter editing is targeted regex, not a full YAML round-trip. That's a deliberate trade-off (see the code comment), but it means it only understands the flat schema documented here: extend it carefully if you add nested frontmatter fields.- Nothing here requires Obsidian specifically, the board is plain markdown with
[[wikilink]]-style card references, but you'll want something that renders wikilinks and checklists nicely to actually look at the board day to day.
Why not just use my PM tool's built-in weekly report? Because it doesn't read your private notes, daily logs, or meeting transcripts, and it can't propose a structured diff for you to approve. This sits upstream of that, deciding what should change before anything gets written.
Does this require Claude Code? No. prompts/ has plain-LLM versions of both
skills that work in any chat interface; you apply the output yourself with
tools/kanban.py.
What if I don't use Obsidian? Nothing in tools/kanban.py or sync/sync.py is
Obsidian-specific. [[wikilink]] syntax in master.md is just text as far as the
scripts are concerned.
Can I add more sections to the diff? Yes, extend parse_diff() in
tools/kanban.py. Keep the fixed section order: it's what makes the walkthrough
predictable session to session.
Why ship CSV/JSON sinks if I actually want Smartsheet? So you can run the whole loop end to end, including tests, with zero credentials and zero API cost, before you wire up anything real. They're also a reasonable break-glass fallback if the real push ever fails: the row table doesn't disappear.
MIT. See LICENSE.