When a task is added, COSTA generates a proposed action — a draft message, document outline, or checklist — and attaches it to the task. By the time you open the task, the work is already started.
Task lists are good at capturing intent and terrible at reducing the cost of acting on it. A task that says "follow up with Ana about the design review" tells you what to do but none of how. Every time you look at it, you reconstruct: when did you last talk, what was agreed, what does a good follow-up in this context look like? That reconstruction is not thinking — it is overhead that arrives before the actual work begins.
The problem compounds for tasks that require drafting. A message to a colleague, a document outline, a review brief — the task is almost entirely the act of producing text. The block is not knowing what to say but starting from nothing, in a moment when you already have seventeen other things in motion. Most of these tasks are not hard. They are just friction-generating in the worst possible place: the transition between deciding something should happen and making it happen.
Proposed actions are built to eliminate that gap. When a task is added to the system, COSTA reads the surrounding context — your most recent 1-on-1 notes with the person, the org structure, your writing style — and generates the output before you have asked for it. The task is not waiting for you to begin; it is waiting for you to review.
The pipeline from task creation to usable proposed action runs automatically. There is no command to trigger it and no setup required per task. Adding a new task to tasks.md is sufficient.
The sequence is: the file watcher detects a change to tasks.md, checks whether any new tasks have appeared by comparing hashes against a state file, and if so calls the /api/tasks/generate-action route. The route runs an agent loop that loads relevant context and writes the proposed action to proposed/YYYY-MM-DD-slug.md. Once the file is written, the route reads tasks.md again and appends an > Action: line to the task entry. The task list is now the index into the proposed action. The UI reads that line to surface the button.
The full cycle — from saving a task to having a proposed action stamped and ready — typically takes fifteen to thirty seconds, depending on the complexity of the task and how much context the agent needs to load.
The trigger lives in the main app page. A Server-Sent Events connection to /api/watch delivers filesystem events in real time. When an event arrives with path === 'tasks.md' and a type of change or add, the client fires a POST to /api/tasks/generate-action. Nothing else is required: no database polling, no cron job, no manual trigger.
The route's first check is a per-process lock. A module-level generating flag blocks concurrent invocations. If the route is already running — because the file watcher fired a second event during the first agent loop — the second call returns immediately with { skipped: 'busy' }. This prevents file-watcher cascade loops, where a write to tasks.md (the Action stamp at the end of generation) would itself trigger another generation cycle.
The state file at context/task-action-state.json holds a flat array of MD5 hashes, one per processed task. Each hash covers the task's first line and all its continuation lines (the > From: and any other metadata). When the route runs, it parses every open task from tasks.md, computes its hash, and looks it up in the state file. Only tasks with unseen hashes and no existing > Action: line are candidates for generation. The route processes one per invocation and returns — the next watcher event picks up the next pending task.
The generation step is an agent loop running against a tightly scoped system prompt. The prompt does not try to produce good output from nothing — it instructs the agent to load context first, then generate. This ordering is load-bearing. A draft Slack message to a direct report produced without knowing anything about that person will be generic. The same message produced after reading two months of 1-on-1 notes will be specific.
The agent's prescribed steps, in order: load context/agents/organisation/people.md for org structure, load context/agents/stuart/writing-style.md for voice, then check for task-specific context. If the task mentions a person, it scans 1-1s/ and loads their most recent note. If the task is document- or writing-oriented, it checks notes/ and meeting-notes/ for relevant material. Only then does it generate the proposed action and write it to the target path.
The output format is determined by the task's area tag. The agent is given explicit format instructions per tag, with no default to a generic "here is a draft" framing. The file begins with the task description verbatim as a heading, followed immediately by the content — no preamble, no meta-commentary.
The target file path is passed in the user message, not computed inside the agent. The route builds proposed/YYYY-MM-DD-slug.md before starting the loop, where the slug is derived from the first six meaningful words of the task text with metadata stripped. The agent's only responsibility is to write to the path it was given and return that path as its final output.
In the task list, tasks with a proposed action show a small ↗ ACT button alongside the task text. Clicking it opens the proposed action document in the content panel — the same panel used for notes, briefs, and 1-on-1s. Nothing is sent or executed automatically. The button presents the work for review; what happens next is up to you.
The button only appears when three conditions are met: the task has an > Action: line in tasks.md, an onOpenFile handler is present in the panel, and the task is not already marked done. Completed tasks do not show the button — there is no point reviewing an action for something that is finished.
The parsing happens in ContentPanel.tsx inside the parseSections function. As it iterates task lines, it looks ahead at continuation lines matching /^ > /. A line starting with Action: is parsed into the task's action field; everything else goes into note. The action field is a relative path — the button passes it directly to onOpenFile, which handles the file fetch.
The proposed action file itself is plain markdown. When opened in the content panel it renders like any other document. You can edit it inline, copy from it, or use it as the basis for something longer. After you have acted on it — sent the message, created the document — you mark the task done and move on. The proposed action file stays in proposed/ but does not clutter the notes view, because that directory is not in the documents page allowlist.
Several of these decisions were not obvious upfront. They emerged from running the system and noticing where it broke.
A proposed action is good when you can act on it without editing it first. That is the bar. Not "useful as a starting point" — that describes most first drafts. The standard is a message you would actually send, a checklist you would actually follow, a review brief you would actually open in the design critique.
The difference between a generic draft and a usable one is almost entirely explained by what context the agent loaded before generating. A draft message to a colleague that opens "Hey, following up on our last conversation about the design review" is generic — the agent loaded nothing. The same task after loading the most recent 1-on-1 note produces something that references the actual work in flight, uses the right framing for that relationship, and matches the communication register.
This means keeping 1-on-1 notes current has a direct payoff in action quality. A stale note from three months ago produces a less specific draft than a note from last week. The system is not magic — it surfaces whatever signal is actually there.
Forcing the agent to produce a draft message rather than "thoughts on a draft message" removes a layer of indirection that would otherwise sit between you and acting. If the output were a summary of what a good message might include, you would still have to write the message. The constraint — produce the exact artefact the task requires — is what makes the action genuinely proposed rather than advisory.
The same logic applies to the no-preamble rule in the file format. An action file that starts "Here is a draft Slack message for your review:" adds nothing. You know it is a draft. You know it is for review. Starting with the content immediately is faster to scan and easier to copy from.
Proposed actions are designed to require review, not to be sent as-is. This is deliberate. A system that automatically dispatches messages or creates documents would be fast but dangerous — the model does not have full context for every interpersonal nuance or organisational sensitivity. The ACT button opens the action for your eyes before anything goes anywhere. The value is in eliminating the blank-slate problem, not in removing human judgment from the loop.
When the quality is consistently weak for a particular type of task, the fix is almost always in the context rather than the prompt. Richer 1-on-1 notes, more specific writing style guidance, better org context — these are the inputs that determine the output ceiling. The agent is a function of what it knows.