Project context
An agent that claims a task in the wrong checkout works without the repo's own conventions and skills. This chapter explains how Ledgenter steers every claim toward the right repo and the right orientation — without ever storing a single repo file.
How Ledgenter connects the work to the actual code.
When agents work on software, Ledgenter ties each task to the real thing that delivered it — the commit, branch, or pull request. It also knows which code repositories a project is connected to, so an agent can tell whether it's working in the right place.
The payoff: a one-glance answer to "what code delivered this task?", and a fast orientation for an agent dropping into an unfamiliar project.
The division of labor
Call it the CLAUDE.md problem. A repository carries its own working
conventions — a CLAUDE.md at the root, procedures under
.claude/skills — and the agent host loads those natively from the
checkout. They only take effect if the agent is actually sitting inside that repo. A
task tracker that hands out cross-repo work can therefore strand an agent: the task says
"fix the parser", but the agent is in the wrong directory, so none of the parser repo's
instructions are in its context.
The tempting fix — have Ledgenter store and serve those files — is deliberately rejected.
A mirrored CLAUDE.md drifts from the real one the moment someone commits;
now there are two sources of truth and the stale one is being injected into agents. So
Ledgenter never stores, mirrors, or serves repo conventions. The split is strict:
the checkout (agent's machine) ledgenter (the server) ────────────────────────────── ────────────────────────────── CLAUDE.md how to work this code projects ↔ repos which repo the work needs .claude/skills repo-local procedures runs.repository which repo you're actually in tasks · briefs the live cross-repo work state loaded natively by the agent host — never mirrors the files on the left — only when you work inside the repo it tells you WHICH repo, and WHAT'S going on
Ledgenter owns exactly what files structurally cannot hold: which repository
a task's project is linked to (via repo_register and repo_link,
chapter 4), which repository the caller's run detected at start
(chapter 6), and the live work state across all of them.
The repo's CLAUDE.md says how to work its code; Ledgenter says
which repo and what's going on. The system's whole job in this chapter is
to correlate those two facts and get the agent physically into the right checkout, where the
native machinery takes over.
repo_match: are you in the right checkout?
Every task_claim and task_get returns a compact
project_context block — roughly a hundred tokens — containing the project
header, the project's linked repos (primary first, capped at ten), and a one-word verdict:
repo_match | Meaning |
|---|---|
match | Your run's detected repo is one of the project's linked repos. No hint tokens are spent confirming it — the verdict itself is the confirmation. |
mismatch | Your run detected a different repo than any the project is linked to. |
unknown | No repo was detected for your session at all (you started outside any checkout, or you're a hosted agent with no filesystem). |
none | The project has no linked repos — non-code work; the question doesn't apply. |
On mismatch or unknown, the claim also carries a structured
hints.repo (expected slug, remote URL, default branch) and a prose
hints.next written for the agent to act on:
{ "ok": true, "claimed": true, "task": { ... },
"project_context": {
"project": { "key": "parser", ... },
"repos": [ { "slug": "acme-parser", "role": "primary", ... } ], // primary first, ≤10
"repo_match": "mismatch"
},
"hints": {
"repo": { "match": "mismatch", "expected_repo_slug": "acme-parser", ... },
"next": "This task's project is linked to repo \"acme-parser\" (…) but your
current run detected a different repo. Work in a checkout of that repo
so its CLAUDE.md and skills load — or task_release if you can't."
} }
The verdict is advisory, never blocking, and that restraint is
load-bearing. The run's repo is detected once, at session start, so it can be stale: an
agent that legitimately switches directories mid-session would otherwise be locked out of
its own work by a guess. Ledgenter tells the truth it knows and lets the agent decide — work in
the right checkout, or task_release the claim back to the pool. (If you do
switch repos mid-session, start a fresh session or pass repository_id
explicitly on code refs.) whoami participates too: it reports up to five
projects linked to the run's current repo, and annotates its ready-task hint when that
task's project lives elsewhere — a pointer suppressed for hosted agents, which have no
checkout to open.
project_brief: one-call orientation
Being in the right repo answers how to work the code. The other half of landing
well is what's going on — and that is project_brief, the one-call
orientation bundle. One request returns:
| Section | Contents | Cap (default / max) |
|---|---|---|
| Header | Key, title, status, owner, dates, description | free text truncated |
| Counts | open_tasks · ready · blocked · in_review · unassigned_ready · done_last_7d | six numbers |
| Top open tasks | Highest priority first | 10 / 25 |
| Recent decisions | Non-superseded only — current rulings, not history | 5 / 10 |
| Knowledge | Titles only, never bodies; runbooks first, then freshest | 5 / 10 |
| Linked repos | Primary first, with open-PR counts | all linked |
| Recent activity | Project, its tasks, and their children | 10 / 25 |
The design constraint is the context window: every section is capped, every free-text
field is truncated, and the whole brief targets ≤ ~900 tokens at default
caps. Knowledge entries are deliberately titles-only — the brief is a map, not the
territory; an agent follows up with knowledge_search on whatever looks
relevant. Implementation-wise it is pure SQL — no embeddings, no external calls — and runs
as security invoker: every SELECT inside it executes under the
caller's own row-level security, so the brief can never show a caller anything a direct
query wouldn't (chapter 5).
Because orientation is needed in different shapes, the same bundle is delivered four ways:
project_brief(project: "parser") // the MCP tool — explicit orientation task_get(task_id, include: ["brief"]) // one-round-trip cold start on a claimed task ledgenter://project/parser/brief // MCP resource — hosts can attach it as context $ ledgenter project brief parser --json // the CLI, for scripts and humans
The machine-local repo map
The server only ever speaks in remote URLs and slugs — local filesystem paths are
deliberately never persisted server-side, a stance the adversarial gates probe directly
(chapter 9). But a hint that says
"clone https://…" is wasteful on a machine that already has the checkout. The
bridge is a small client-side file, ~/.ledgenter/repos.json, mapping the server's
own repo identity — the url_fingerprint, e.g.
github/org/name (or local/<slug> for remoteless repos) — to
an absolute local path:
{
"version": 1,
"repos": {
"github/acme/parser": "C:\\dev\\parser",
"local/scratch-tools": "C:\\dev\\scratch-tools"
}
}
The map populates itself: whenever an agent runs inside a repo, core records that repo's
root after a successful run start (opt out with LEDGENTER_REPO_MAP=0); the explicit
ledgenter repo map command does the same on demand. Core then overlays a
local_path onto repo rows in responses — task_claim,
task_get, project_brief — so the mismatch hint above effectively
becomes "open C:\dev\parser" on this machine. Every operation on the map is
best-effort and never throws: it is an ergonomic overlay, never control flow.
Paths never leave the machine. The overlay is added client-side
after the response arrives, and the telemetry layer structurally drops every
local_path key before anything is persisted — enforcement by shape, not by
policy. The adversarial gate suite includes a probe that attempts to leak a local path
through telemetry and fails the build if one gets through.
Scoping claims to checkouts
The final piece is keeping wrong-repo claims from happening at all.
task_claim accepts a repository_ids filter: claim-next then only
considers tasks whose project is linked to one of the given repos — "only claim
work I can actually check out". A loop launcher (chapter 6)
passes the repos its machine has; ledgenter task claim --mapped-only derives that
list from the local repo map automatically, and refuses with a corrective hint if the map is
empty or matches no registered repo rather than silently filtering everything out.
A narrowed filter creates a failure mode worth designing against: a launcher with a wrong or empty repo map would find nothing to claim, tick after tick, and look idle forever. So a repo-narrowed empty pool is never silent — it reports what exists outside the filter:
{ "ok": true, "claimed": false, "task": null,
"pool_outside_filter": 7,
"hint": "0 claimable task(s) within the given repositories; 7 exist tenant-wide.
Widen repository_ids (is your repo map current?) or drop the filter." }
The distinction matters operationally: zero tasks within the filter and zero tenant-wide means a genuinely quiet workspace; zero within the filter and seven outside means a misconfiguration — and now it looks like one, to the agent and to anyone reading the run record.
Taught, not just built. Agents learn this entire model in-band:
guide('git-and-repos') covers repo registration, code refs, the
repo_match verdicts and what to do about each, and the repo map — so an
agent that has never seen these docs still recovers correctly from a mismatch.