How We Built Charlie, Part 11: Context Without Magic Memory Charlie's context system assembles typed providers into a bounded, sourceable snapshot per run, avoiding an indiscriminate transcript of all past activity. The architecture records provenance and freshness for each entry, with providers fetching concurrently while the rendered prompt remains ordered and inspectable. This design ensures context is verifiable and prevents stale guidance from becoming invisible memory. How We Built Charlie, Part 15: What We Learned Building Charlie What we learned about making AI engineering work owned, bounded, verifiable, and useful to a team. How sourceable, freshness-bound context prevents stale guidance from becoming invisible memory. How We Built Charlie /blog/series/how-we-built-charlie/ continues from Chapter 10 /blog/devboxes-as-the-agents-body/ : the execution environment needs context whose source and freshness are clear. Ask Charlie to review a service and he needs more than the diff. He needs the current repository and its tracked rules. He may need a skill that defines the team’s preferred review procedure. He needs to know which tools are available, what the Task asks for, and which customer or system facts are relevant to that request. He may need recent task history or repository knowledge, but only when its provenance and freshness are clear enough to support the decision. An indiscriminate transcript of everything Charlie has ever seen would be a poor substitute. It would mix old and new state, unrelated customers, superseded instructions, partial investigations, and outputs written for different purposes. More context would make the prompt larger while making authority harder to reason about. Charlie’s context system is an assembly pipeline. Typed providers gather bounded entries from named sources. The runtime validates and gates those providers, runs eligible work concurrently, orders the results deterministically, and records what was included or excluded. The first turn receives a snapshot. Later turns can opt into fresh context, but most providers do not rerun by default. This architecture gives context a provenance. It also gives it an expiration problem. A sourceable snapshot can still become stale after it is fetched. Dependability comes from knowing where a fact came from, when it was assembled, and which sources must be read again before a consequential action. The examples below simplify the public context contracts and omit private operational settings. The implementation can evolve while the durable idea remains: context should be sourceable and inspectable, with each entry kept as a bounded record. Metadata and outcomes that apply more broadly can live at the provider or run level. Providers can fetch concurrently while the rendered prompt remains ordered, bounded, and inspectable. The Task carries the current objective and durable lifecycle. The transcript records model-visible chronology. Context is material assembled for a run or turn to help the model reason about that objective. Those objects overlap in content, but they have different jobs. A Task can reference a repository without containing its rules. A transcript can record a prior tool result without proving that result is still current. A context provider can summarize available repositories without granting permission to edit all of them. | Object | Primary role | Freshness model | |---|---|---| | Task | Durable objective, lineage, lifecycle, and current request | Changes through scheduler and mailbox protocols | | Run log | Ordered model-visible messages, calls, and results | Append-oriented within one execution | | Context | Selected source material for a run or turn | Snapshot determined by provider source and run policy | | Artifact | External or repository state used as evidence | Must be read back when current state matters | Calling all four “memory” hides the boundaries that make them useful. The model can remember a tool result because it appears in the current transcript. The runtime can reconstruct provider choices because it persisted context metadata. A later run can read a repository rule again. None of those mechanisms implies universal recall across every Task. A context provider declares its identity, category, configuration, capability requirements, and run function. The output is a list of entries rather than an arbitrary prompt string. That lets the runner apply common validation, ordering, timeout, budgeting, and reporting rules. ContextProvider name category skills | memory system | rules required capabilities turn policy timeout and token budget run request - entries The contract does not force every provider to use the same source. A rules provider reads tracked files. A skills provider discovers SKILL.md files. A system provider may report runtime versions or currently reusable devboxes. Another provider can query customer-scoped knowledge or recent Task facts. What they share is the surrounding behavior. The runtime can say that a provider was included, empty, timed out, capability-gated, turn-gated, invalid, or failed. Prompt construction does not have to infer those outcomes from missing text. This also keeps source-specific complexity out of the model prompt. Git reads, CLI calls, repository discovery, and API parsing happen before rendering. The prompt receives labeled entries and warnings rather than raw integration machinery. Charlie’s provider categories are skills , memory , system , and rules . The names support grouping and metadata order. skills describes discoverable procedures the agent can apply. memory covers selected persisted knowledge products when an agent configuration includes them. system describes runtime, tool, repository-inventory, or execution-environment facts. rules carries repository-owned instructions and constraints.A category does not make an entry authoritative by itself. A rules entry is useful because its provider can identify the tracked source file and revision. A system entry may be a best-effort command snapshot. A memory entry can be well sourced and still need a freshness check. A skill can define a procedure without supplying current facts about the repository. Current Charlie agent configurations select a particular provider set for a role. An entry agent and a focused worker do not need identical context. The selected agent configuration is therefore part of provenance: it tells us which providers were even eligible to contribute. Provider calls can be independent. Reading repository rules should not wait for a version command when both can run safely at the same time. Charlie runs eligible providers concurrently to keep startup latency bounded. Concurrency would be dangerous if completion timing determined prompt order. A slow provider would move around from run to run, making prompts and debugging harder to compare. The context runner separates execution order from rendering order. Eligible providers run in parallel. Their results are then rendered according to stable policy, including cache-stability or declared position and entry order. Provider-run metadata also has a canonical category order. The exact ordering mode can evolve with an agent version, but it is selected intentionally rather than inherited from network timing. eligible = validate + gate providers results = run concurrently eligible, with timeout, prompt = render results, stable ordering policy, This gives retries a stable target. It also makes prompt snapshots meaningful in tests. A provider can become slower without silently changing instruction precedence. Most context providers default to the first turn. That is where Charlie needs the initial repository rules, skills index, environment snapshot, and customer or system facts selected for the run. On turn zero, the context is merged into the initial user prompt with the Task content. The transcript does not add a second standalone local:context:0 message, because that would duplicate the same material. On later turns, providers configured for all turns may run again. If they produce non-empty context, the kernel appends one deterministic user message for that turn, identified conceptually as local:context: