How We Built Charlie, Part 2: The Task, Not the Chat
A chat can start an engineering request. A durable task keeps it owned while people add context, work is delegated, systems fail, and Charlie reports what happened.
A prompt can finish maintenance once. A Daemon owns a recurring, repository-defined job, including when to act, verify, no-op, or escalate.
“Check the open pull requests, fix any failing CI, update docs that drifted, and see whether our dependencies need attention.”
That is a fairly useful prompt. A good agent can inspect the repository, gather context, make a focused change, run verification, and hand back something reviewable.
A good prompt can produce a good maintenance pass but it does not yet answer the questions a recurring job needs answered every time: what can wake it, what evidence counts, how far it may reach, how it verifies its work, and what a correct no-op or escalation looks like.
Interactive prompting remains the right interface for work that is new, ambiguous, temporary, or still taking shape. It gives an engineer direct control over the question and lets the request absorb context that may not belong in a permanent job definition.
Once the same job should happen next week, define it in the repository as a bounded role in the form of a daemon.
A Charlie Daemon gives that recurring job an owner. Its repository-defined contract states when it can wake, what evidence it should gather, which routines it may perform, how it verifies its work, and which conditions require no change or human judgment. An activation is bounded and completes. Charlie is not a continuously running process, and the role does not grant open-ended authority.
The difference becomes clearer when the prompt and the operating contract sit next to each other.
| Concern | Occasional prompt | Daemon-owned maintenance |
|---|---|---|
| Starting point | An engineer initiates a task | A watch event or schedule can wake it |
| Context | Supplied for this request | Gathered by a repo-defined contract |
| Frequency | When an engineer chooses to run it | Repeated bounded activations |
| Depth | Depends on the prompt | Required evidence and checks are explicit |
| Judgment and safety | Decided during the task | Limits, deny rules, and escalation are reviewable |
| Ownership | Ends with the response | Remains defined as a role in the repo |
A complete activation is not necessarily a change. Depending on the evidence, the right result may be a patch, a message, or a verified no-op.
The examples below are current Daemon contracts from the Charlie Labs daemons repository. Each turns a plausible maintenance prompt into a job with repeatable depth and explicit boundaries.
An occasional request might be:
“CI is red on this PR. Fix it.”
That can work well. The engineer points Charlie at the pull request, adds any relevant intent, and reviews the proposed repair. But the prompt leaves several operational questions open. Which failure should Charlie own when five checks fail? Is the branch current? May he change dependencies to make the build pass? Should he resolve a merge conflict? What should happen if another engineer is already pushing a fix?
The PR Check Repair Daemon answers those questions before an activation begins.
It can wake when a check run, check suite, or commit status reports a non-successful result on the current head of a non-draft pull request. Its evidence set includes the triggering check logs, provider data, local reproduction, repository context, the PR diff, and clear PR intent. The role owns the triggering failure, not every red check it happens to see.
The boundaries do most of the useful work. The role must not refresh the branch from base, resolve merge conflicts, make product or security decisions just to satisfy CI, change external provider configuration, or push a speculative repair. It also checks for concurrent repair activations and refreshes the remote head before editing and pushing.
A formatting failure with an obvious fix can result in a focused commit. An ambiguous test failure that depends on product intent should result in a stop and a concise request for human judgment. A stale trigger or a failure already fixed by another activation should produce a no-op. All three outcomes are complete applications of the job.
The prompted version sounds straightforward:
“Look at recent changes and update any docs that are stale.”
The hidden decisions arrive quickly. How recent is recent? Which changed files imply documentation impact? Should generated reference files be edited directly? How many unrelated doc gaps should be bundled into one PR? What if an engineer already opened a documentation update?
The Recent Docs Drift Maintainer is scheduled to run on weekdays. It inspects merged pull requests and source, configuration, or workflow changes, using the past three business days when the previous successful run is unclear. Stale documentation is not evidence that the old behavior still works.
The job allows at most one focused documentation PR per activation. It excludes runtime code, tests, migrations, repository configuration, broad rewrites, and generated output, and it checks open documentation PRs before creating another one.
No clear, safe, source-backed target means no change. That covers ambiguous impact, generated targets, and active human-owned work on the same documentation. The boundary prevents guessed behavior, duplicate work, and opportunistic rewrites.
“Update our JavaScript dependencies” is easy to ask and expensive to interpret.
Does it include major versions? Should runtime and development dependencies move together? Which package manager should be used in a mixed repository? How large may the upgrade become before it stops being routine maintenance? Is a refactor acceptable when a package breaks an API?
The JavaScript/TypeScript Dependency Update Maintainer is scheduled to run weekly. It discovers the configured package manager and matching lockfile, scans for available updates, and limits its default scope to patch and minor versions. Runtime and development dependencies go into separate grouped pull requests.
The limits are concrete: no package manager migration, registry change, workspace reorganization, auto-merge, or unrelated refactor. A run can create or update at most two pull requests, with no more than 20 packages in either group. After updating, Charlie refreshes the lockfile with the configured package manager, runs repository verification, and checks that the diff contains only the intended dependency changes and minimal generated metadata.
No eligible updates, no safe package-manager match, or overlapping human-owned work means leaving the repository alone. If verification fails and the repair would broaden into feature or migration work, Charlie stops or leaves a clearly explained draft.
A one-off prompt can make all of those decisions too. The advantage of the Daemon is that the team makes them once in a reviewable policy instead of renegotiating scope every Monday.
Reporting jobs expose another weakness in prompted maintenance: the human has to ask for the report, then decide whether the result deserved a message at all.
Consider:
“Summarize yesterday’s important GitHub activity in Slack.”
What counts as important? Are label changes meaningful? Should Monday include the weekend? Should one ordinary item produce a post? How do we avoid publishing the same digest twice?
The GitHub Activity Digest is scheduled for weekdays at 15:00 UTC. It uses the previous scheduled run as its normal window, with Monday covering activity since Friday. It selects changes that affect what the team needs to know or do: merged pull requests, work ready for review, unblocked PRs, or recurring CI failures affecting active work.
Label churn, assignment changes, bot housekeeping, and comment activity without a resulting action stay out. Fewer than two meaningful items normally means no message, unless the single item is a critical blocker or unblocker. Before posting, the job checks the configured Slack channel for an equivalent digest on the same UTC date.
The useful output here is often silence. A low-signal day does not need a low-signal summary proving that automation ran. Ownership includes protecting the team’s attention.
The same pattern applies outside GitHub. A prompt such as “research this Linear bug and add context” may produce a strong one-time triage note. The Linear Bug Context Researcher makes the role repeatable.
It can wake for a newly created Linear issue that appears to describe a bug or regression. It searches recent related Linear and GitHub evidence in a defined order, limits the number of links, and posts only when the findings improve triage. It may not change issue fields or act on GitHub. Weak matches, non-bugs, missing repository mapping, and equivalent prior comments require no new comment.
The contract preserves a useful division of labor: Charlie gathers source-backed context; engineers still decide priority, ownership, and the fix.
DAEMON.md puts the job definition in the repoA Daemon lives at .agents/daemons/<daemon-id>/DAEMON.md. Plain Markdown and YAML frontmatter put the role’s behavior in the same review process as code and configuration.
id, purpose, and routines are required. deny is optional. A Daemon must define at least one activation mechanism: either watch or schedule (or both). The Markdown body records details such as evidence, limits, verification, coordination, and communication.
A wake condition can start a bounded activation; it does not authorize every action. The team can review and change the job definition in a pull request as its tolerance, tooling, or repository changes.
The Daemons documentation covers the wake/execute model and file contract. Choosing daemons is useful when deciding whether a recurring job is observable, bounded, reviewable, and valuable enough to own this way.
Prompts remain the right interface for new investigations, temporary pushes, unusual migrations, product questions, and work that depends on context an engineer is still forming. That interaction is valuable because the human can steer as the problem changes.
Maintenance is different once the team knows the job should recur. Repeated prompting keeps initiation, scope-setting, and completion checks attached to a person each time. A Daemon puts those decisions into a reviewable contract the team can inspect and improve.
Prompt Charlie when the work is new. Give Charlie a Daemon when the job should still be happening next week.