Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions .agents/donna/intro.donna.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,10 @@ Artifact type tags:

## Journaling

You MUST use `donna journal write` to track your actions and thoughts, according the description in `{{ donna.lib.view("./usage/cli.donna.md") }}`.
Donna creates internal journal records for important workflow events, according to the description in `{{ donna.lib.view("./usage/cli.donna.md") }}`.

Journaling is a required part of workflow execution. An action request MUST be considered incomplete until required journal records are written.
Journal records can be forwarded to a third-party tool by configuring `[journal].cmd` in `<project-root>/.donna/config.toml`.

Journaling lifecycle for each non-trivial action request:
The configured command is a list of command arguments. Arguments whose first and last characters are `{` and `}` are replaced with attributes of `JournalRecord`.

1. Start intent (`Goal:`) before substantial work begins.
2. Progress updates (`Step:`) at significant phase boundaries.
3. Concrete edits (`Change:`) after meaningful source/artifact update batches.
4. Completion handoff (`Step:`) before calling `sessions action-request-completed`.

Journal records MUST be change/decision-oriented and SHOULD be sufficient for another agent to continue work without re-discovery.

If you perform a long operation (e.g., exploring the codebase, designing a solution) that takes more than 10 seconds, you MUST journal your progress.

You MUST use `donna journal view --lines 100` to read the last records after you compress your context.

If your work is interrupted and you resume later, you MUST first journal `Resume context and next action`.
If `[journal].cmd` is omitted, Donna treats it as `None` and performs no journal writing.
56 changes: 26 additions & 30 deletions .agents/donna/usage/cli.donna.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@ Donna renders cells differently, depending on the protocol used.

### Commands

There are four sets of commands:
There are three sets of commands:

- `donna -p <protocol> workspaces …` — manages workspaces. Most-likely it will be used once per your project to initialize it.
- `donna -p <protocol> sessions …` — manages sessions. You will use these commands to start, push forward, and manage your work.
- `donna -p <protocol> artifacts …` — manages artifact discovery, reading, and validation.
- `donna -p <protocol> journal …` — manages session actions journal. You will use these commands to log and inspect the history of actions performed during the session.

Use:

Expand Down Expand Up @@ -162,42 +161,39 @@ The format of `<artifact-pattern>` is as follows:

CLI arguments MUST NOT use relative artifact paths like `./...` or `../../...`; use absolute `@/...` paths or rooted wildcard forms.

### Working with journal
### Journal integrations

Use the next commands to work with session journal:
Donna creates internal `JournalRecord` values for important workflow events.
Donna does not expose a journal CLI command.

- `donna -p <protocol> journal write <message>` — record a single new entry to the journal with the given **single-line** `message` (newlines are not allowed). Donna automatically adds a timestamp and other relevant information to the journal entry.
- `donna -p <protocol> journal view [--lines N] [--follow]` — display journal records.
To forward journal records to a third-party tool, configure the workspace
`<project-root>/.donna/config.toml` file:

Agents MUST use `donna -p <protocol> journal write <message>` to log:

- Goals of the long-running agent-side operations: `Goal: <goal description>`.
- Significant steps of the long-running agent-side operations: `Step: <phase progress or completion handoff>`.
- Significant thoughts during the long-running operations: `Thought: <important thought>`.
- Significant assumptions during the long-running operations: `Assumption: <important assumption>`.
- Changes in the project source code or in the project structure: `Change: <what changed and where>`.

For each non-trivial action request, agents MUST follow this journaling contract:

1. Write exactly one `Goal:` record at action-request start.
2. Write `Step:` records at significant phase boundaries. If an action request describes a multi-step process, there MUST be at least one `Step:` record per specified step and one `Step:` record for the completion handoff.
3. Write `Change:` records after each meaningful source update batch.
4. Write one final `Step:` record immediately before `sessions action-request-completed`.
```toml
[journal]
cmd = ["cli-tool", "--message", "{message}"]
```

Agents MUST consider these cases as significant phase boundaries:
`cmd` is a list of command arguments. If an argument starts with `{` and ends
with `}`, Donna treats the whole argument as a `JournalRecord` attribute name
and replaces it with that value. Donna validates placeholders when loading
config.

- A work phase expected to take more than 10 seconds.
- Transition from analysis/research to implementation/editing.
- Transition to a new step in a multi-step process described in the action request.
- Start or completion of a multi-file or multi-artifact change batch.
- A decision that changes implementation direction.
Supported attributes:

Before `sessions action-request-completed`, agents MUST check journal completeness for the current action request.
- `timestamp` — record creation time, formatted as ISO-8601.
- `actor_id` — actor that created the record; empty string when unknown.
- `message` — single-line journal message.
- `current_task_id` — current task id; empty string when no task is active.
- `current_work_unit_id` — current work unit id; empty string when no work unit is active.
- `current_operation_id` — current operation artifact section id; empty string when no operation is active.

Agents MUST NOT log:
If `journal.cmd` is omitted, Donna treats it as `None` and performs no journal
writing.

- CLI commands they execute.
- Elementary/trivial steps.
Donna still prints newly created internal journal records immediately using the
selected protocol formatter, so agents receive live feedback even when no
external journal command is configured.

## IMPORTANT ON DONNA TOOL USAGE

Expand Down
2 changes: 1 addition & 1 deletion .agents/donna/usage/worlds.donna.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Donna has read access to artifacts stored in the project world. It discovers, fe

Developers and external tools are responsible for mutating project artifacts before Donna reads or validates them.

Donna still writes its own session state and journal data under `<project-root>/.donna/session`, but that internal state storage is separate from world-artifact mutation.
Donna still writes its own session state under `<project-root>/.donna/session`, but that internal state storage is separate from world-artifact mutation.

## Intro Artifacts

Expand Down
19 changes: 19 additions & 0 deletions .agents/skills/session/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: session
description: Manage task-local temporary working files in the project `.session/` directory. Use whenever an agent needs to create scratch files, notes, temporary plans, intermediate reasoning artifacts, generated helper files, or other task-scoped temporary data; also use when the user asks to start a new session.
---

# Session

## Temporary Files

- Store every temporary file created during work on a task under `<project-root>/.session/`.
- Create `<project-root>/.session/` before creating temporary files if the directory does not already exist.
- Use files in `<project-root>/.session/` for task-scoped temporary information such as intermediate notes, plans, scratch artifacts, and working data.
- Do not place temporary task files outside `<project-root>/.session/`.

## New Sessions

When the user tells you to start a new session, remove all files and directories inside `<project-root>/.session/`.

Keep the `.session` directory itself available for later temporary files when practical.
2 changes: 2 additions & 0 deletions .donna/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[journal]
cmd = ["./bin/taskwarior.sh", "log", "+journal", "+donna", "kind:event", "{message}"]
12 changes: 12 additions & 0 deletions .taskrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
data.location=.session/taskwarrior
confirmation=no

uda.kind.type=string
uda.kind.label=Kind
uda.kind.values=goal,step,thought,assumption,change,event,

uda.logged_at.type=string
uda.logged_at.label=Logged

uda.logged_time.type=string
uda.logged_time.label=Time
50 changes: 50 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,53 @@ You MUST use it to:
You MUST NOT use it for:

- Implementing huge features or behaviors that require adding massive blocks of code (like adding a new class, module, writing a huge function, etc.).

### `task`

`task` — Taskwarrior — is the project journal for significant agent-side work.

You MUST use it to write journal records with these exact command templates from the project root:

```bash
./bin/taskwarior.sh log +journal +agent kind:goal "<goal description>"
./bin/taskwarior.sh log +journal +agent kind:step "<phase progress or completion handoff>"
./bin/taskwarior.sh log +journal +agent kind:thought "<important thought>"
./bin/taskwarior.sh log +journal +agent kind:assumption "<important assumption>"
./bin/taskwarior.sh log +journal +agent kind:change "<what changed and where>"
```

Journal messages MUST be single-line strings.

You MUST log:

- Goals of long-running agent-side operations with `kind:goal`.
- Significant steps of long-running operations with `kind:step`.
- Significant thoughts during long-running operations with `kind:thought`.
- Significant assumptions during long-running operations with `kind:assumption`.
- Changes in project source code or project structure with `kind:change`.

You MAY add extra tags after `+agent` and before the message:

```bash
./bin/taskwarior.sh log +journal +agent kind:<message-kind> +<extra-tag>... "<single-line journal message>"
```

For each non-trivial Donna action request or long-running agent task:

1. Write exactly one `goal` record at action-request or task start.
2. Write `step` records at significant phase boundaries.
3. Write `change` records after each meaningful source update batch.
4. Write one final `step` record immediately before reporting completion or handing work back to the developer.

You MUST consider these cases significant phase boundaries:

- A work phase expected to take more than 10 seconds.
- Transition from analysis or research to implementation.
- Transition to a new step in a multi-step process.
- Start or completion of a multi-file or multi-artifact change batch.
- A decision that changes implementation direction.

You MUST NOT log:

- CLI commands you execute.
- Elementary or trivial steps.
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Your agent will generate [state machines](https://en.wikipedia.org/wiki/Finite-s

Donna allows your agent to execute hundreds of consecutive steps without swaying away from the defined workflow. Branching, loops, nested calls, and recursion — all possible.

![Journal log demonstration](./docs/images/journal-demo.gif)

## What is Donna?

Donna is a CLI tool that helps coding agents like Codex focus on the task at hand by keeping high-level control flow in explicit Donna workflows. Donna dictates what should be done at each step of the work, so the agent can focus on the actual piece.
Expand Down Expand Up @@ -162,18 +160,33 @@ If you upgrade Donna later, run `donna workspaces update` to refresh `.agents/do

**Donna is a CLI tool for agents.** You rarely need to use it directly.

However, it is convenient to run `donna journal view --follow` in a separate terminal to see what is going on in the current session.

Commands you may need:

- `donna workspaces init` — Initialize Donna in your project.
- `donna sessions start` — start a new working session, remove everything from the previous session.
- `donna artifacts list <pattern>` — list artifacts with short descriptions.
- `donna journal view [--lines N] [--follow]` — view the log of work performed in the current session.

Here is an example of the real Donna session work log:
Donna can send internal journal records to a third-party tool. Configure it in `.donna/config.toml`:

```toml
[journal]
cmd = ["cli-tool", "--message", "{message}"]
```

`cmd` is a list of command arguments. If an argument starts with `{` and ends with `}`, Donna treats the whole argument as a `JournalRecord` attribute name and replaces it with that value. Donna validates placeholders when loading config.

Supported attributes:

- `timestamp` — record creation time, formatted as ISO-8601.
- `actor_id` — actor that created the record; empty string when unknown.
- `message` — single-line journal message.
- `current_task_id` — current task id; empty string when no task is active.
- `current_work_unit_id` — current work unit id; empty string when no work unit is active.
- `current_operation_id` — current operation artifact section id; empty string when no operation is active.

By default `journal.cmd` is omitted and Donna treats it as `None`, so no journal writing is performed.

![Journal log demonstration](./docs/images/journal-demo.gif)
Donna still prints newly created internal journal records immediately using the selected protocol formatter, so agents receive live feedback even when no external journal command is configured.

Use `donna --help` for a quick reference.

Expand Down
136 changes: 136 additions & 0 deletions bin/journal-follow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env python3

import argparse
import json
import subprocess
import sys
import time
from datetime import UTC, datetime
from pathlib import Path
from typing import Any


ROOT_DIR = Path(__file__).resolve().parent.parent
TASKWARIOR = ROOT_DIR / "bin" / "taskwarior.sh"
COLUMN_PADDING = 2


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Follow project journal records.")
parser.add_argument("-n", "--lines", type=int, default=20, help="number of existing records to show")
parser.add_argument("-i", "--interval", type=float, default=1.0, help="poll interval in seconds")
return parser.parse_args()


def load_records() -> list[dict[str, Any]]:
result = subprocess.run(
[str(TASKWARIOR), "rc.verbose:nothing", "+journal", "export"],
cwd=ROOT_DIR,
check=True,
stdout=subprocess.PIPE,
text=True,
)

return json.loads(result.stdout)


def record_key(record: dict[str, Any]) -> tuple[str, str, str]:
return (
str(record.get("logged_at") or ""),
str(record.get("entry") or ""),
str(record.get("uuid") or ""),
)


def record_id(record: dict[str, Any]) -> str:
uuid = record.get("uuid")

if uuid:
return str(uuid)

return "|".join(record_key(record) + (str(record.get("description") or ""),))


def record_time(record: dict[str, Any]) -> str:
logged_time = record.get("logged_time")

if logged_time:
return str(logged_time)

entry = str(record.get("entry") or "")

if not entry:
return ""

try:
return datetime.strptime(entry, "%Y%m%dT%H%M%SZ").replace(tzinfo=UTC).astimezone().strftime("%H:%M:%S")
except ValueError:
return ""


def record_actor(record: dict[str, Any]) -> str:
return " ".join(str(tag) for tag in record.get("tags", []) if tag != "journal")


def record_kind(record: dict[str, Any]) -> str:
return str(record.get("kind") or "")


class Formatter:
def __init__(self) -> None:
self.actor_width = 0
self.kind_width = 0

def observe(self, records: list[dict[str, Any]]) -> None:
for record in records:
self.actor_width = max(self.actor_width, len(record_actor(record)))
self.kind_width = max(self.kind_width, len(record_kind(record)))

def format_record(self, record: dict[str, Any]) -> str:
actor_width = self.actor_width + COLUMN_PADDING
kind_width = self.kind_width + COLUMN_PADDING

return "{time} {actor:<{actor_width}} {kind:<{kind_width}} {description}".format(
time=record_time(record),
actor=record_actor(record),
actor_width=actor_width,
kind=record_kind(record),
kind_width=kind_width,
description=str(record.get("description") or ""),
).rstrip()


def print_records(records: list[dict[str, Any]], formatter: Formatter) -> None:
for record in records:
print(formatter.format_record(record), flush=True)


def main() -> int:
args = parse_args()
records = sorted(load_records(), key=record_key)
formatter = Formatter()
formatter.observe(records)
seen = {record_id(record) for record in records}

if args.lines > 0:
print_records(records[-args.lines :], formatter)

while True:
time.sleep(args.interval)

records = sorted(load_records(), key=record_key)
formatter.observe(records)
new_records = [record for record in records if record_id(record) not in seen]

for record in new_records:
seen.add(record_id(record))

print_records(new_records, formatter)


if __name__ == "__main__":
try:
raise SystemExit(main())
except KeyboardInterrupt:
print(file=sys.stderr)
raise SystemExit(130)
Loading
Loading