Introduction

Many developers have encountered a frustrating phenomenon when working with Codex. You give it a clear instruction, such as normalizing all tags to lowercase and removing duplicate entries. The model returns a confirmation message claiming that the code has been fully processed. However, once you inspect the output file, you discover unexpected changes. Tag ordering may get rearranged, empty string values remain in the dataset, or the model only writes a high-level execution plan without implementing the actual function. There is another more subtle failure mode: test commands exit with a success status code, yet the test suite only validates the simplest happy path and ignores edge cases.

These failures are rarely caused by prompts that are too short. More often, task definitions miss three critical components: persistent long-term project rules, the scope of deliverables for the current task, and objective criteria that define a truly completed job. This article uses a small practical example, the normalizeTags function, to break down the workflow. It does not rely on heavy custom frameworks.

OpenAI’s official usage guidance for GPT-6 Astra states that prompt design must define instruction boundaries, clarification triggers, capability limits and measurable testing standards. This article translates those principles into a runnable Node.js example, demonstrating how to reduce meaningless empty work when using coding agents.

Store Long-Term Rules in AGENTS.md, Avoid Repeating Them in Every Prompt

Assume you are building a code library. Permanent project constraints should be stored inside AGENTS.md at the project root directory, rather than being pasted repeatedly into every individual prompt.

Sample rules written in AGENTS.md:

1. Functions under the /src directory must remain pure functions. No file reads, no mutation of input parameters.
2. Do not add new dependencies. Do not modify package.json.
3. Testing uses node --test. When modifying utility functions, developers must add boundary test cases.
4. Do not generate dist folders or build artifacts.

Rules like these apply to the whole repository, not just a single tag normalization task. The agent reads rule files starting from the project root and traverses down to the working directory. Rules defined in deeper directories supplement or override higher-level specifications. With this setup, prompt authors do not need to repeatedly remind the agent “do not modify package.json”. The prompt only needs to specify the scope and business logic of the current task.

This separation brings clear engineering benefits. Team members update project conventions once inside AGENTS.md, instead of editing hundreds of separate prompt snippets. The coding agent maintains consistent behavior across all tasks in the repository. When multiple AI agents operate on the same codebase, this shared rule file reduces conflicts caused by inconsistent assumptions.

Task Prompts Should Only Describe Business Semantics for the Current Work

The following task description can be sent directly to execution-focused coding agents. It grants permission to modify two target files, eliminating the need for repeated confirmation for low-risk changes.

Task: Implement and test normalizeTags(tags).

Scope: Only src/normalizeTags.mjs and test/normalizeTags.test.mjs can be modified.
Do not install dependencies, edit package.json, or access external networks.

Business Semantics:
1. Input must be an array of strings. Throw TypeError immediately if non-string values are found.
2. Each tag trims leading and trailing whitespace, converts all characters to lowercase, and collapses consecutive internal whitespace into a single space.
3. Discard empty tags. Preserve the first occurrence order when deduplicating entries.
4. Do not mutate the original input array.

Acceptance criteria:
Run node --test test/normalizeTags.test.mjs
The test suite must cover normalization logic, deduplication ordering, input immutability, and rejection of non-string values.

Stop conditions:
If target files are locked by other edits, requirements force public API changes, or the task must expand outside the permitted file scope, stop work and report conflicts. Do not expand scope automatically.

The key insight here is narrowing down the decision space. The word “deduplicate” alone leaves many ambiguous choices. Should the system retain the first matching entry or the last one? Should results be sorted alphabetically? Should non-string entries be skipped or trigger an explicit error? Without written specifications, code may be syntactically valid yet violate business expectations.

The prompt does not attempt to describe implementation details such as using Set objects or for loops. It focuses on observable behavior. This means developers can swap underlying implementation methods later without rewriting acceptance tests or task prompts.

Write Acceptance Criteria as Runnable node:test Code

Below is a complete implementation file. Save it as normalizeTags.test.mjs. Run the test command node --test normalizeTags.test.mjs. The function and tests are placed together for quick demonstration. In real production projects, the core function moves into src/normalizeTags.mjs, and test cases remain inside the test folder. The test suite uses only Node.js standard libraries, without network access or external model calls.

import test from "node:test";
import assert from "node:assert/strict";

export function normalizeTags(tags) {
    if (!Array.isArray(tags)) throw new TypeError("tags must be an array");
    const seen = new Set();
    const output = [];
    for (const raw of tags) {
        if (typeof raw !== "string") throw new TypeError("tag must be a string");
        const tag = raw.trim().toLowerCase().replace(/\s+/g, " ");
        if (tag && !seen.has(tag)) {
            seen.add(tag);
            output.push(tag);
        }
    }
    return output;
}

test("Normalize whitespace, deduplicate, preserve first occurrence order", () => {
    const input = [" JavaScript ", "A  B", "node.js", "  ", "Node.JS"];
    assert.deepEqual(normalizeTags(input), ["javascript", "a b", "node.js"]);
});

test("Original input array remains unmodified", () => {
    const input = ["A  B "];
    normalizeTags(input);
    assert.deepEqual(input, ["A  B "]);
});

test("Empty arrays and all-whitespace entries produce defined results", () => {
    assert.deepEqual(normalizeTags([]), []);
    assert.deepEqual(normalizeTags(["   ", "\t"]), []);
});

test("Non-string values trigger defined stop signal: TypeError", () => {
    assert.throws(() => normalizeTags(["ok", 7]), TypeError);
    assert.throws(() => normalizeTags("ok"), TypeError);
});

The third and fourth test groups are especially important. Empty input arrays, fully blank tag strings and numeric values are typical edge cases ignored by basic happy-path testing. Converting numeric types silently into strings can introduce hidden upstream data corruption. Throwing an explicit TypeError converts ambiguous guesswork into clear stop signals.

These tests do not prove that one large model outperforms another. They only verify that this JavaScript implementation satisfies the written contract for the defined input cases. When coding agents produce code, runnable tests serve as objective proof of completion, instead of relying on natural language self-reporting.

Name Tests to Express Business Promises, Not Implementation Details

Poorly written test names describe implementation mechanics such as “calls toLowerCase” or “uses Set to track elements”. These names couple tests to one specific implementation approach and fail to communicate what end users actually receive.

In the example above, test names describe business guarantees: “preserves first occurrence order”, “does not mutate input”, “rejects invalid types”. If the underlying implementation later switches from for loops to reduce functions or alternative data containers, these tests remain valid, as long as business behavior stays unchanged.

This is the reason acceptance checklists should focus on outcomes, not actions. Avoid phrasing like “add deduplication code”. Instead, write requirements such as “duplicate tags retain the order of their first occurrence”, “consecutive internal whitespace collapses to one single space”, “invalid data types throw specified errors”. When failures appear, test logs directly show which business promise has been broken. Maintainers do not need to reverse-engineer the original intent of the test.

Acceptance Checklists Validate Final Outcomes, Not Merely Actions

After the agent finishes execution, validate deliverables following this ordered checklist:

  1. Confirm only permitted source and test files have been modified.
  2. Execute node --test normalizeTags.test.mjs and verify full test pass status. A command exit code of zero alone is insufficient validation.
  3. Inspect deduplicated outputs to confirm the first-occurrence ordering rule is respected.
  4. Verify non-string inputs are rejected as specified.
  5. Check whether conflicts exist that require scope expansion.

Item five is not intended to slow down tooling. Tasks confined to new files with clear boundaries can proceed directly. Work must pause when agents attempt to edit files modified by other developers, alter public API definitions, adjust build configurations, or access external data sources. This rule prevents the agent from silently expanding task scope and pretending success in risky situations.

Prevent “Empty Work” and Hand Off Verification to the Next Commit

AGENTS.md stores static long-term engineering conventions. Task prompts define current input, output and boundaries. Test suites create verifiable facts that prove completion. This separation of responsibilities simplifies root-cause analysis during review. Engineers can quickly identify whether failures stem from missing rules, incomplete business semantics, or omitted edge cases in acceptance tests.

The next time you assign a small function modification task to Codex, avoid vague phrases like “be careful and detailed”. Instead, explicitly list the two files allowed for edits, define expected outputs, and state the exact conditions that force the agent to stop. This style of prompt is closer to formal project work orders than wordy natural language instructions.

Teams managing multiple model endpoints for coding agents may use Treerouter, an API gateway, to route requests and manage authentication for different model services. Separating prompt logic, test validation and gateway traffic control creates a more robust workflow for AI-assisted software development.

Conclusion

GPT-6 Astra and Codex coding agents frequently produce false-positive “completed” results. This common issue is not solved by writing longer prompts. The reliable solution is splitting project rules, task definitions and acceptance verification into separate artifacts.

AGENTS.md holds permanent repository rules. Task prompts only describe business behavior and scope for a single assignment. Acceptance tests are executable code that validates the final output against the defined contract. Test cases should target edge inputs including empty arrays, whitespace-only strings and invalid value types. Test naming should focus on business promises instead of implementation techniques. The acceptance checklist confirms modified files, test results, data behavior and scope conflicts.

This pattern reduces empty work, scope creep and self-reported success from coding agents. It brings AI-assisted programming closer to standard software engineering workflows, with objective test results as the source of truth. Teams building agent-based development pipelines can reuse this prompt structure across different repositories and programming languages.

Learn more:https://treerouter.com